Repository: numaproj/numaflow

Issue:
Latency metrics not visited or not documented in Reduce UDF
**Describe the bug**

As far as I check up on the Golang `pkg/` directory with `grep -ir` and VScode, some latency metrics are documented in the [Operator Manual](https://numaflow.numaproj.io/operations/metrics/metrics/) but not visited in Reduce UDF. And other some ones are visited but not documented. Please see the following table for deatils.

| Metric name | Described in the Operator Manual | Visited in Reduce UDF |
| --- | --- | --- |
| forwarder_read_processing_time             | yes | no  |
| forwarder_write_processing_time            | yes | no  |
| forwarder_ack_processing_time              | yes | no  |
| forwarder_udf_processing_time              | yes | no  |
| forwarder_concurrent_udf_processing_time   | no  | no  |
| forwarder_forward_chunk_processing_time    | yes | no  |
| reduce_pnf_process_time                    | yes | no  |
| reduce_pnf_forward_time                    | yes | no  |
| reduce_pbq_write_time                      | no  | yes |

**Expected behavior**

- reduce_pbq_write_time -- Documented.
- forwarder_udf_processing_time --  ~~Visited in case of non-concurrent Reduce UDF.~~ __Documented as it is only visited in Map UDF.__
- forwarder_concurrent_udf_processing_time -- ~~Documented. Also visited in case of concurrent Reduce UDF.~~ __Documented as it is only visited in Map UDF.__
- forwarder_forward_chunk_processing_time -- Documented as it is only visited in Map UDF.
- The others -- Visited.

---


<!-- Issue Author: Don't delete this message to encourage other users to support your issue! -->

**Message from the maintainers**:

Impacted by this bug? Give it a 👍. We often sort issues this way to know what to prioritize.

>For quick help and support, join our [slack](https://numaproj.slack.com/join/shared_invite/zt-19svuv47m-YKHhsQ~~KK9mBv1E7pNzfg#/shared-invite/email) channel.


Discussion:

Solution patch:
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 4f54ae5f..1cb99993 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -79,6 +79,9 @@ jobs:
       - name: Start Pulsar standalone container
         run: docker run -d -p 6650:6650 -p 8080:8080 apachepulsar/pulsar:4.0.0 bin/pulsar standalone
 
+      - name: Start Kafka container
+        run: docker run -d -p 9092:9092 --name kafka-broker apache/kafka:4.0.0
+
       - name: Set up Go 1.x
         uses: actions/setup-go@v5
         with:
@@ -157,32 +160,16 @@ jobs:
 
   build-rust-amd64:
     runs-on: ubuntu-24.04
-    defaults:
-      run:
-        working-directory: ./rust
     steps:
       - uses: actions/checkout@v4
-      - name: Setup Rust toolchain
-        uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
-        with:
-          cache-workspaces: rust -> target
-          rustflags: ""
-      - name: Configure sccache
-        run: |
-          echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
-          echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
-      - name: Run sccache-cache
-        uses: mozilla-actions/sccache-action@v0.0.9
-      - name: Install dependencies
-        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
-      - name: Print Protoc version
-        run: protoc --version
+      # TODO: Use actions/cache@v4 to cache target/ directory
       - name: Build binary
-        run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu
+        run: |
+          docker run -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh amd64
       - name: Rename binaries
         run: |
-          cp -pv target/x86_64-unknown-linux-gnu/release/numaflow ./numaflow-rs-linux-amd64
-          cp -pv target/x86_64-unknown-linux-gnu/release/entrypoint ./entrypoint-linux-amd64
+          cp -pv rust/target/x86_64-unknown-linux-gnu/release/numaflow ./numaflow-rs-linux-amd64
+          cp -pv rust/target/x86_64-unknown-linux-gnu/release/entrypoint ./entrypoint-linux-amd64
       - name: Verify numaflow binary is Statically Linked
         run: |
           file ./numaflow-rs-linux-amd64
@@ -195,13 +182,13 @@ jobs:
         uses: actions/upload-artifact@v4
         with:
           name: numaflow-rs-linux-amd64
-          path: rust/numaflow-rs-linux-amd64
+          path: numaflow-rs-linux-amd64
           if-no-files-found: error
       - name: Upload entrypoint binary
         uses: actions/upload-artifact@v4
         with:
           name: entrypoint-linux-amd64
-          path: rust/entrypoint-linux-amd64
+          path: entrypoint-linux-amd64
           if-no-files-found: error
 
   e2e-tests:
diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml
index c52853f9..29484b0a 100644
--- a/.github/workflows/nightly-build.yml
+++ b/.github/workflows/nightly-build.yml
@@ -42,81 +42,61 @@ jobs:
 
   build-rust-amd64:
     runs-on: ubuntu-24.04
-    defaults:
-      run:
-        working-directory: ./rust
     steps:
       - uses: actions/checkout@v4
-      - name: Setup Rust toolchain
-        uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
-        with:
-          cache-workspaces: rust -> target
-          rustflags: ""
-      - name: Configure sccache
-        run: |
-          echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
-          echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
-      - name: Run sccache-cache
-        uses: mozilla-actions/sccache-action@v0.0.9
-      - name: Install dependencies
-        run: sudo apt-get update && sudo apt-get install -y protobuf-compiler
+      # TODO: Use actions/cache@v4 to cache target/ directory
       - name: Build binary
-        run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu
-      - name: Rename binary
         run: |
-          cp -pv target/x86_64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-amd64
-          cp -pv target/x86_64-unknown-linux-gnu/release/entrypoint entrypoint-linux-amd64
+          docker run -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh amd64
+      - name: Rename binaries
+        run: |
+          cp -pv rust/target/x86_64-unknown-linux-gnu/release/numaflow ./numaflow-rs-linux-amd64
+          cp -pv rust/target/x86_64-unknown-linux-gnu/release/entrypoint ./entrypoint-linux-amd64
+      - name: Verify numaflow binary is Statically Linked
+        run: |
+          file ./numaflow-rs-linux-amd64
+          file ./numaflow-rs-linux-amd64 | grep -q 'static-pie linked'
+      - name: Verify entrypoint binary is Statically Linked
+        run: |
+          file ./entrypoint-linux-amd64
+          file ./entrypoint-linux-amd64 | grep -q 'static-pie linked'
       - name: Upload numaflow binary
         uses: actions/upload-artifact@v4
         with:
           name: numaflow-rs-linux-amd64
-          path: rust/numaflow-rs-linux-amd64
+          path: numaflow-rs-linux-amd64
+          if-no-files-found: error
       - name: Upload entrypoint binary
         uses: actions/upload-artifact@v4
         with:
           name: entrypoint-linux-amd64
-          path: rust/entrypoint-linux-amd64
+          path: entrypoint-linux-amd64
           if-no-files-found: error
 
   build-rust-arm64:
-    runs-on: ubuntu-24.04
-    defaults:
-      run:
-        working-directory: ./rust
+    runs-on: ubuntu-24.04-arm
     steps:
       - uses: actions/checkout@v4
       - name: Update Rust Toolchain Target
         run: |
-          echo "targets = ['aarch64-unknown-linux-gnu']" >> rust-toolchain.toml
-      - name: Setup Rust toolchain
-        uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
-        with:
-          cache-workspaces: rust -> target
-          rustflags: ""
-      - name: Configure sccache
-        run: |
-          echo "RUSTC_WRAPPER=sccache" >> $GITHUB_ENV
-          echo "SCCACHE_GHA_ENABLED=true" >> $GITHUB_ENV
-      - name: Run sccache-cache
-        uses: mozilla-actions/sccache-action@v0.0.9
-      - name: Install dependenices
-        run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu protobuf-compiler
+          echo "targets = ['aarch64-unknown-linux-gnu']" >> rust/rust-toolchain.toml
       - name: Build binary
-        run: RUSTFLAGS='-C target-feature=+crt-static -C linker=aarch64-linux-gnu-gcc' cargo build --release --target aarch64-unknown-linux-gnu
+        run: |
+          docker run -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh aarch64
       - name: Rename binary
         run: |
-          cp -pv target/aarch64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-arm64
-          cp -pv target/aarch64-unknown-linux-gnu/release/entrypoint entrypoint-linux-arm64
+          cp -pv rust/target/aarch64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-arm64
+          cp -pv rust/target/aarch64-unknown-linux-gnu/release/entrypoint entrypoint-linux-arm64
       - name: Upload numaflow binary
         uses: actions/upload-artifact@v4
         with:
           name: numaflow-rs-linux-arm64
-          path: rust/numaflow-rs-linux-arm64
+          path: numaflow-rs-linux-arm64
       - name: Upload entrypoint binary
         uses: actions/upload-artifact@v4
         with:
           name: entrypoint-linux-arm64
-          path: rust/entrypoint-linux-arm64
+          path: entrypoint-linux-arm64
 
   build-push-linux-multi:
     name: Build & push linux/amd64 and linux/arm64
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3da9dac2..82103447 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -40,67 +40,60 @@ jobs:
 
   build-rust-amd64:
     runs-on: ubuntu-24.04
-    defaults:
-      run:
-        working-directory: ./rust
     steps:
       - uses: actions/checkout@v4
-      - name: Setup Rust toolchain
-        uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
-        with:
-          rustflags: ""
-      - name: Install dependencies
-        run: sudo apt-get install -y protobuf-compiler
       - name: Build binary
-        run: RUSTFLAGS='-C target-feature=+crt-static' cargo build --release --target x86_64-unknown-linux-gnu
-      - name: Rename binary
         run: |
-          cp -pv target/x86_64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-amd64
-          cp -pv target/x86_64-unknown-linux-gnu/release/entrypoint entrypoint-linux-amd64
+          docker run -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh amd64
+      - name: Rename binaries
+        run: |
+          cp -pv rust/target/x86_64-unknown-linux-gnu/release/numaflow ./numaflow-rs-linux-amd64
+          cp -pv rust/target/x86_64-unknown-linux-gnu/release/entrypoint ./entrypoint-linux-amd64
+      - name: Verify numaflow binary is Statically Linked
+        run: |
+          file ./numaflow-rs-linux-amd64
+          file ./numaflow-rs-linux-amd64 | grep -q 'static-pie linked'
+      - name: Verify entrypoint binary is Statically Linked
+        run: |
+          file ./entrypoint-linux-amd64
+          file ./entrypoint-linux-amd64 | grep -q 'static-pie linked'
       - name: Upload numaflow binary
         uses: actions/upload-artifact@v4
         with:
           name: numaflow-rs-linux-amd64
-          path: rust/numaflow-rs-linux-amd64
+          path: numaflow-rs-linux-amd64
+          if-no-files-found: error
       - name: Upload entrypoint binary
         uses: actions/upload-artifact@v4
         with:
           name: entrypoint-linux-amd64
-          path: rust/entrypoint-linux-amd64
+          path: entrypoint-linux-amd64
           if-no-files-found: error
 
   build-rust-arm64:
-    runs-on: ubuntu-24.04
-    defaults:
-      run:
-        working-directory: ./rust
+    runs-on: ubuntu-24.04-arm
     steps:
       - uses: actions/checkout@v4
       - name: Update Rust Toolchain Target
         run: |
-          echo "targets = ['aarch64-unknown-linux-gnu']" >> rust-toolchain.toml
-      - name: Setup Rust toolchain
-        uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
-        with:
-          rustflags: ""
-      - name: Install dependencies
-        run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu protobuf-compiler
+          echo "targets = ['aarch64-unknown-linux-gnu']" >> rust/rust-toolchain.toml
       - name: Build binary
-        run: RUSTFLAGS='-C target-feature=+crt-static -C linker=aarch64-linux-gnu-gcc' cargo build --release --target aarch64-unknown-linux-gnu
+        run: |
+          docker run -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh aarch64
       - name: Rename binary
         run: |
-          cp -pv target/aarch64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-arm64
-          cp -pv target/aarch64-unknown-linux-gnu/release/entrypoint entrypoint-linux-arm64
+          cp -pv rust/target/aarch64-unknown-linux-gnu/release/numaflow numaflow-rs-linux-arm64
+          cp -pv rust/target/aarch64-unknown-linux-gnu/release/entrypoint entrypoint-linux-arm64
       - name: Upload numaflow binary
         uses: actions/upload-artifact@v4
         with:
           name: numaflow-rs-linux-arm64
-          path: rust/numaflow-rs-linux-arm64
+          path: numaflow-rs-linux-arm64
       - name: Upload entrypoint binary
         uses: actions/upload-artifact@v4
         with:
           name: entrypoint-linux-arm64
-          path: rust/entrypoint-linux-arm64
+          path: entrypoint-linux-arm64
 
   build-push-linux-multi:
     name: Build & push linux/amd64 and linux/arm64
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3a2e1c50..84823a9a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,41 @@
 # Changelog
 
+## v1.5.0-rc3 (2025-05-18)
+
+ * [40839123](https://github.com/numaproj/numaflow/commit/408391237923536f482099a1040f02b104832375) Update manifests to v1.5.0-rc3
+ * [abc72d3b](https://github.com/numaproj/numaflow/commit/abc72d3bb4e1f70d990ee07dcc5dc51030b5d768) fix: potential incorrect k8s minor version (#2647)
+ * [f3968694](https://github.com/numaproj/numaflow/commit/f396869467d3b78ced4dfcc23267d082155911d4) feat: exponential backoff retry strategy for sink (#2614)
+ * [b3a691a4](https://github.com/numaproj/numaflow/commit/b3a691a414241e6a74016f0a7cebd224b924c078) fix: honour retry config in case of Pipeline in Rust (#2638)
+ * [35c80b14](https://github.com/numaproj/numaflow/commit/35c80b14f2edc74dfb1cc078393776703e1231de) fix: broken retry strategy in sink (#2626)
+
+### Contributors
+
+ * Adarsh Jain
+ * Derek Wang
+ * Yashash H L
+
+## v1.5.0-rc2 (2025-05-12)
+
+ * [bec31d7f](https://github.com/numaproj/numaflow/commit/bec31d7fd7fd444b3db33f7a97ca71fe19b96675) Update manifests to v1.5.0-rc2
+ * [89e1d4f7](https://github.com/numaproj/numaflow/commit/89e1d4f724168864541bf09c9493088b03f88d68) fix: sink instantiation (#2627)
+ * [ee713734](https://github.com/numaproj/numaflow/commit/ee7137347ed091179bdc64bef4523e9d85a81b40) feat: sqs sink (#2615)
+ * [8ede89a7](https://github.com/numaproj/numaflow/commit/8ede89a7c4ea73fc7878148a839c6f42df6eaf31) fix: source and transformer metrics (#2584)
+ * [10f7bd2b](https://github.com/numaproj/numaflow/commit/10f7bd2b0be9aa5fff596c3599bf2f3ca8354623) feat: enable pod template support for serving deployment (#2620)
+ * [532c236d](https://github.com/numaproj/numaflow/commit/532c236d42878228dafeaa2c0f268e7067738005) fix: serving example (#2619)
+ * [9430f277](https://github.com/numaproj/numaflow/commit/9430f277bf40b26b1af82cf69d67c252e5850013) fix: take only the base filename while sorting (#2616)
+ * [4bd05a7f](https://github.com/numaproj/numaflow/commit/4bd05a7f01be010961af2408c343fbc696905b0f) fix: idle watermark severity (#2613)
+ * [868addb0](https://github.com/numaproj/numaflow/commit/868addb02c7da8b40a5d7b81aa89a3aedec37db2) doc: minor typo in accum doc (#2611)
+ * [af29f159](https://github.com/numaproj/numaflow/commit/af29f15900a91d9ecca05abb8ed731ae0432aa13) doc: accumulator doc (#2609)
+ * [224c8a4e](https://github.com/numaproj/numaflow/commit/224c8a4e6cdae5e2cd2195d2f6d82bca5c8fcaf7) feat: PBQ for Reduce Async Data Movement (#2601)
+
+### Contributors
+
+ * Derek Wang
+ * Shrivardhan Rao
+ * Takashi Menjo
+ * Vigith Maurice
+ * shrivardhan
+
 ## v1.5.0-rc1 (2025-04-30)
 
  * [834446ff](https://github.com/numaproj/numaflow/commit/834446ff701f1dd9ef2cdc347179e0544ce1c14a) Update manifests to v1.5.0-rc1
@@ -133,6 +169,25 @@
  * dependabot[bot]
  * shrivardhan
 
+## v1.4.6 (2025-05-18)
+
+ * [b966b1bb](https://github.com/numaproj/numaflow/commit/b966b1bbc88337fcec8580dea3417fbc3710ce2e) Update manifests to v1.4.6
+
+### Contributors
+
+ * Derek Wang
+
+## v1.4.5 (2025-05-13)
+
+ * [9090935a](https://github.com/numaproj/numaflow/commit/9090935aafcaf6fc173bc27312c2a09bef22cec4) Update manifests to v1.4.5
+ * [62c45609](https://github.com/numaproj/numaflow/commit/62c4560996e384657009d2230ca925ffec60499b) feat: expose metrics for pipeline/mvtx desired and current phase (#2563)
+ * [6e49fb11](https://github.com/numaproj/numaflow/commit/6e49fb114361f6c451ffd4ab4beac40b63aae259) fix: broken retry strategy in pipeline sink (#2625)
+
+### Contributors
+
+ * Derek Wang
+ * Yashash H L
+
 ## v1.4.4 (2025-04-03)
 
  * [ae9d93ad](https://github.com/numaproj/numaflow/commit/ae9d93ad0aaaf61854696b1638fd7d813dda7cc8) Update manifests to v1.4.4
diff --git a/Dockerfile b/Dockerfile
index 7588ad0f..2aec6b63 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,7 +25,7 @@ RUN chmod +x /bin/entrypoint
 FROM lukemathwalker/cargo-chef:latest-rust-1.86 AS chef
 ARG TARGETPLATFORM
 WORKDIR /numaflow
-RUN apt-get update && apt-get install -y protobuf-compiler
+RUN apt-get update && apt-get install -y protobuf-compiler cmake
 
 FROM chef AS planner
 COPY ./rust/ .
diff --git a/Makefile b/Makefile
index 793c38ef..1d442fac 100644
--- a/Makefile
+++ b/Makefile
@@ -205,7 +205,7 @@ build-rust-in-docker:
 .PHONY: build-rust-in-docker-multi
 build-rust-in-docker-multi:
 	mkdir -p dist
-	docker run -v ./dist/cargo:/root/.cargo -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh
+	docker run -v ./dist/cargo:/root/.cargo -v ./rust/:/app/ -w /app --rm ubuntu:24.04 bash build.sh all
 	cp -pv rust/target/aarch64-unknown-linux-gnu/release/numaflow dist/numaflow-rs-linux-arm64
 	cp -pv rust/target/x86_64-unknown-linux-gnu/release/numaflow dist/numaflow-rs-linux-amd64
 	cp -pv rust/target/aarch64-unknown-linux-gnu/release/entrypoint dist/entrypoint-linux-arm64
@@ -311,7 +311,7 @@ docs-serve: docs
 
 .PHONY: docs-linkcheck
 docs-linkcheck: /usr/local/bin/lychee
-	lychee --exclude-path=CHANGELOG.md *.md --include "https://github.com/numaproj/*" $(shell find ./test -type f) $(wildcard ./docs/*.md)
+	lychee --exclude-path=CHANGELOG.md --exclude-path=./docs/APIs.md --exclude "https://localhost:*" --exclude "http://localhost:*" --exclude "http://127.0.0.1*" *.md $(shell find ./test -type f) $(shell find ./docs -name '*.md')
 
 # pre-push checks
 
diff --git a/USERS.md b/USERS.md
index 87cd2cfc..dd4864d8 100644
--- a/USERS.md
+++ b/USERS.md
@@ -3,7 +3,7 @@
 Please add your company name and initial use case (optional) below.
 
 1. [Intuit](https://www.intuit.com/) - Streaming ML inference and to prepare data for ML model training in real-time.
-2. [B|Cubed](https://bcubed-corp.com/) - Digital Signal Processing Communication Systems.  We receive RF energy and use numaflows to transform the RF to individual bits into intelligible data.
+2. [B|Cubed](https://bcubed.com/) - Digital Signal Processing Communication Systems. We receive RF energy and use numaflows to transform the RF to individual bits into intelligible data.
 3. [Atlan](https://atlan.com/) - Numaflow powers real time notifications, stream processing ecosystem at Atlan.
 4. [Valegachain Analytics](https://www.valegachain.com/) Numaflow is used to extract, transform, and load cryptocurrency blocks and mempool transactions in data lakes, as well as for activity alerts.
 5. [Lockheed Martin](https://lockheedmartin.com/) Perform ELT processing on high and low volume data streams of sensor data as recieved from IOT type systems.
diff --git a/api/json-schema/schema.json b/api/json-schema/schema.json
index af5e8279..10593caa 100644
--- a/api/json-schema/schema.json
+++ b/api/json-schema/schema.json
@@ -19576,6 +19576,10 @@
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.ServeSink",
           "description": "Serve sink is used to return results when working with a ServingPipeline."
         },
+        "sqs": {
+          "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SqsSink",
+          "description": "SQS sink is used to write the data to the AWS SQS."
+        },
         "udsink": {
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDSink",
           "description": "UDSink sink is used to write the data to the user-defined sink."
@@ -19756,12 +19760,26 @@
     "io.numaproj.numaflow.v1alpha1.Backoff": {
       "description": "Backoff defines parameters used to systematically configure the retry strategy.",
       "properties": {
+        "cap": {
+          "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Duration",
+          "description": "A limit on revised values of the interval parameter. If a multiplication by the factor parameter would make the interval exceed the cap then the interval is set to the cap and the steps parameter is set to zero."
+        },
+        "factor": {
+          "description": "Interval is multiplied by factor each iteration, if factor is not zero and the limits imposed by Steps and Cap have not been reached.",
+          "format": "double",
+          "type": "number"
+        },
         "interval": {
           "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Duration",
-          "description": "Interval sets the delay to wait before retry, after a failure occurs."
+          "description": "Interval sets the initial retry duration, after a failure occurs."
+        },
+        "jitter": {
+          "description": "The sleep at each iteration is the interval plus an additional amount chosen uniformly at random from the interval between zero and `jitter*interval`.",
+          "format": "double",
+          "type": "number"
         },
         "steps": {
-          "description": "Steps defines the number of times to try writing to a sink including retries",
+          "description": "Steps defines the maximum number of retry attempts",
           "format": "int64",
           "type": "integer"
         }
@@ -21897,11 +21915,11 @@
       "type": "object"
     },
     "io.numaproj.numaflow.v1alpha1.RetryStrategy": {
-      "description": "RetryStrategy struct encapsulates the settings for retrying operations in the event of failures. It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.",
+      "description": "The RetryStrategy struct defines the configuration for handling operation retries in case of failures. It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.",
       "properties": {
         "backoff": {
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.Backoff",
-          "description": "BackOff specifies the parameters for the backoff strategy, controlling how delays between retries should increase."
+          "description": "BackOff specifies the parameters for the exponential backoff strategy, controlling how delays between retries should increase."
         },
         "onFailure": {
           "description": "OnFailure specifies the action to take when the specified retry strategy fails. The possible values are: 1. \"retry\": start another round of retrying the operation, 2. \"fallback\": re-route the operation to a fallback sink and 3. \"drop\": drop the operation and perform no further action. The default action is to retry.",
@@ -22441,6 +22459,10 @@
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.ServeSink",
           "description": "Serve sink is used to return results when working with a ServingPipeline."
         },
+        "sqs": {
+          "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SqsSink",
+          "description": "SQS sink is used to write the data to the AWS SQS."
+        },
         "udsink": {
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDSink",
           "description": "UDSink sink is used to write the data to the user-defined sink."
@@ -22501,6 +22523,28 @@
       },
       "type": "object"
     },
+    "io.numaproj.numaflow.v1alpha1.SqsSink": {
+      "properties": {
+        "awsRegion": {
+          "description": "AWSRegion is the AWS Region where the SQS queue is located",
+          "type": "string"
+        },
+        "queueName": {
+          "description": "QueueName is the name of the SQS queue",
+          "type": "string"
+        },
+        "queueOwnerAWSAccountID": {
+          "description": "QueueOwnerAWSAccountID is the queue owner aws account id",
+          "type": "string"
+        }
+      },
+      "required": [
+        "awsRegion",
+        "queueName",
+        "queueOwnerAWSAccountID"
+      ],
+      "type": "object"
+    },
     "io.numaproj.numaflow.v1alpha1.SqsSource": {
       "description": "SqsSource represents the configuration of an AWS SQS source",
       "properties": {
diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json
index d27f65d4..42e4b9f0 100644
--- a/api/openapi-spec/swagger.json
+++ b/api/openapi-spec/swagger.json
@@ -19581,6 +19581,10 @@
           "description": "Serve sink is used to return results when working with a ServingPipeline.",
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.ServeSink"
         },
+        "sqs": {
+          "description": "SQS sink is used to write the data to the AWS SQS.",
+          "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SqsSink"
+        },
         "udsink": {
           "description": "UDSink sink is used to write the data to the user-defined sink.",
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDSink"
@@ -19761,12 +19765,26 @@
       "description": "Backoff defines parameters used to systematically configure the retry strategy.",
       "type": "object",
       "properties": {
+        "cap": {
+          "description": "A limit on revised values of the interval parameter. If a multiplication by the factor parameter would make the interval exceed the cap then the interval is set to the cap and the steps parameter is set to zero.",
+          "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Duration"
+        },
+        "factor": {
+          "description": "Interval is multiplied by factor each iteration, if factor is not zero and the limits imposed by Steps and Cap have not been reached.",
+          "type": "number",
+          "format": "double"
+        },
         "interval": {
-          "description": "Interval sets the delay to wait before retry, after a failure occurs.",
+          "description": "Interval sets the initial retry duration, after a failure occurs.",
           "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.Duration"
         },
+        "jitter": {
+          "description": "The sleep at each iteration is the interval plus an additional amount chosen uniformly at random from the interval between zero and `jitter*interval`.",
+          "type": "number",
+          "format": "double"
+        },
         "steps": {
-          "description": "Steps defines the number of times to try writing to a sink including retries",
+          "description": "Steps defines the maximum number of retry attempts",
           "type": "integer",
           "format": "int64"
         }
@@ -21883,11 +21901,11 @@
       }
     },
     "io.numaproj.numaflow.v1alpha1.RetryStrategy": {
-      "description": "RetryStrategy struct encapsulates the settings for retrying operations in the event of failures. It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.",
+      "description": "The RetryStrategy struct defines the configuration for handling operation retries in case of failures. It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.",
       "type": "object",
       "properties": {
         "backoff": {
-          "description": "BackOff specifies the parameters for the backoff strategy, controlling how delays between retries should increase.",
+          "description": "BackOff specifies the parameters for the exponential backoff strategy, controlling how delays between retries should increase.",
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.Backoff"
         },
         "onFailure": {
@@ -22428,6 +22446,10 @@
           "description": "Serve sink is used to return results when working with a ServingPipeline.",
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.ServeSink"
         },
+        "sqs": {
+          "description": "SQS sink is used to write the data to the AWS SQS.",
+          "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.SqsSink"
+        },
         "udsink": {
           "description": "UDSink sink is used to write the data to the user-defined sink.",
           "$ref": "#/definitions/io.numaproj.numaflow.v1alpha1.UDSink"
@@ -22487,6 +22509,28 @@
         }
       }
     },
+    "io.numaproj.numaflow.v1alpha1.SqsSink": {
+      "type": "object",
+      "required": [
+        "awsRegion",
+        "queueName",
+        "queueOwnerAWSAccountID"
+      ],
+      "properties": {
+        "awsRegion": {
+          "description": "AWSRegion is the AWS Region where the SQS queue is located",
+          "type": "string"
+        },
+        "queueName": {
+          "description": "QueueName is the name of the SQS queue",
+          "type": "string"
+        },
+        "queueOwnerAWSAccountID": {
+          "description": "QueueOwnerAWSAccountID is the queue owner aws account id",
+          "type": "string"
+        }
+      }
+    },
     "io.numaproj.numaflow.v1alpha1.SqsSource": {
       "description": "SqsSource represents the configuration of an AWS SQS source",
       "type": "object",
diff --git a/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml b/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml
index 38327ab4..85a8d9d6 100644
--- a/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml
+++ b/config/base/crds/full/numaflow.numaproj.io_monovertices.yaml
@@ -3673,6 +3673,19 @@ spec:
                         type: object
                       serve:
                         type: object
+                      sqs:
+                        properties:
+                          awsRegion:
+                            type: string
+                          queueName:
+                            type: string
+                          queueOwnerAWSAccountID:
+                            type: string
+                        required:
+                        - awsRegion
+                        - queueName
+                        - queueOwnerAWSAccountID
+                        type: object
                       udsink:
                         properties:
                           container:
@@ -4247,9 +4260,15 @@ spec:
                     properties:
                       backoff:
                         properties:
+                          cap:
+                            type: string
+                          factor:
+                            type: number
                           interval:
                             default: 1ms
                             type: string
+                          jitter:
+                            type: number
                           steps:
                             format: int32
                             type: integer
@@ -4260,6 +4279,19 @@ spec:
                     type: object
                   serve:
                     type: object
+                  sqs:
+                    properties:
+                      awsRegion:
+                        type: string
+                      queueName:
+                        type: string
+                      queueOwnerAWSAccountID:
+                        type: string
+                    required:
+                    - awsRegion
+                    - queueName
+                    - queueOwnerAWSAccountID
+                    type: object
                   udsink:
                     properties:
                       container:
diff --git a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml
index d876cf57..00e60b4f 100644
--- a/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml
+++ b/config/base/crds/full/numaflow.numaproj.io_pipelines.yaml
@@ -8363,6 +8363,19 @@ spec:
                               type: object
                             serve:
                               type: object
+                            sqs:
+                              properties:
+                                awsRegion:
+                                  type: string
+                                queueName:
+                                  type: string
+                                queueOwnerAWSAccountID:
+                                  type: string
+                              required:
+                              - awsRegion
+                              - queueName
+                              - queueOwnerAWSAccountID
+                              type: object
                             udsink:
                               properties:
                                 container:
@@ -8937,9 +8950,15 @@ spec:
                           properties:
                             backoff:
                               properties:
+                                cap:
+                                  type: string
+                                factor:
+                                  type: number
                                 interval:
                                   default: 1ms
                                   type: string
+                                jitter:
+                                  type: number
                                 steps:
                                   format: int32
                                   type: integer
@@ -8950,6 +8969,19 @@ spec:
                           type: object
                         serve:
                           type: object
+                        sqs:
+                          properties:
+                            awsRegion:
+                              type: string
+                            queueName:
+                              type: string
+                            queueOwnerAWSAccountID:
+                              type: string
+                          required:
+                          - awsRegion
+                          - queueName
+                          - queueOwnerAWSAccountID
+                          type: object
                         udsink:
                           properties:
                             container:
diff --git a/config/base/crds/full/numaflow.numaproj.io_servingpipelines.yaml b/config/base/crds/full/numaflow.numaproj.io_servingpipelines.yaml
index fef16a64..b9da5651 100644
--- a/config/base/crds/full/numaflow.numaproj.io_servingpipelines.yaml
+++ b/config/base/crds/full/numaflow.numaproj.io_servingpipelines.yaml
@@ -8342,6 +8342,19 @@ spec:
                                   type: object
                                 serve:
                                   type: object
+                                sqs:
+                                  properties:
+                                    awsRegion:
+                                      type: string
+                                    queueName:
+                                      type: string
+                                    queueOwnerAWSAccountID:
+                                      type: string
+                                  required:
+                                  - awsRegion
+                                  - queueName
+                                  - queueOwnerAWSAccountID
+                                  type: object
                                 udsink:
                                   properties:
                                     container:
@@ -8916,9 +8929,15 @@ spec:
                               properties:
                                 backoff:
                                   properties:
+                                    cap:
+                                      type: string
+                                    factor:
+                                      type: number
                                     interval:
                                       default: 1ms
                                       type: string
+                                    jitter:
+                                      type: number
                                     steps:
                                       format: int32
                                       type: integer
@@ -8929,6 +8948,19 @@ spec:
                               type: object
                             serve:
                               type: object
+                            sqs:
+                              properties:
+                                awsRegion:
+                                  type: string
+                                queueName:
+                                  type: string
+                                queueOwnerAWSAccountID:
+                                  type: string
+                              required:
+                              - awsRegion
+                              - queueName
+                              - queueOwnerAWSAccountID
+                              type: object
                             udsink:
                               properties:
                                 container:
diff --git a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml
index e84a9fda..5243cd8e 100644
--- a/config/base/crds/full/numaflow.numaproj.io_vertices.yaml
+++ b/config/base/crds/full/numaflow.numaproj.io_vertices.yaml
@@ -3152,6 +3152,19 @@ spec:
                         type: object
                       serve:
                         type: object
+                      sqs:
+                        properties:
+                          awsRegion:
+                            type: string
+                          queueName:
+                            type: string
+                          queueOwnerAWSAccountID:
+                            type: string
+                        required:
+                        - awsRegion
+                        - queueName
+                        - queueOwnerAWSAccountID
+                        type: object
                       udsink:
                         properties:
                           container:
@@ -3726,9 +3739,15 @@ spec:
                     properties:
                       backoff:
                         properties:
+                          cap:
+                            type: string
+                          factor:
+                            type: number
                           interval:
                             default: 1ms
                             type: string
+                          jitter:
+                            type: number
                           steps:
                             format: int32
                             type: integer
@@ -3739,6 +3758,19 @@ spec:
                     type: object
                   serve:
                     type: object
+                  sqs:
+                    properties:
+                      awsRegion:
+                        type: string
+                      queueName:
+                        type: string
+                      queueOwnerAWSAccountID:
+                        type: string
+                    required:
+                    - awsRegion
+                    - queueName
+                    - queueOwnerAWSAccountID
+                    type: object
                   udsink:
                     properties:
                       container:
diff --git a/config/install.yaml b/config/install.yaml
index 1cdd2345..031f53dd 100644
--- a/config/install.yaml
+++ b/config/install.yaml
@@ -6864,6 +6864,19 @@ spec:
                         type: object
                       serve:
                         type: object
+                      sqs:
+                        properties:
+                          awsRegion:
+                            type: string
+                          queueName:
+                            type: string
+                          queueOwnerAWSAccountID:
+                            type: string
+                        required:
+                        - awsRegion
+                        - queueName
+                        - queueOwnerAWSAccountID
+                        type: object
                       udsink:
                         properties:
                           container:
@@ -7438,9 +7451,15 @@ spec:
                     properties:
                       backoff:
                         properties:
+                          cap:
+                            type: string
+                          factor:
+                            type: number
                           interval:
                             default: 1ms
                             type: string
+                          jitter:
+                            type: number
                           steps:
                             format: int32
                             type: integer
@@ -7451,6 +7470,19 @@ spec:
                     type: object
                   serve:
                     type: object
+                  sqs:
+                    properties:
+                      awsRegion:
+                        type: string
+                      queueName:
+                        type: string
+                      queueOwnerAWSAccountID:
+                        type: string
+                    required:
+                    - awsRegion
+                    - queueName
+                    - queueOwnerAWSAccountID
+                    type: object
                   udsink:
                     properties:
                       container:
@@ -18252,6 +18284,19 @@ spec:
                               type: object
                             serve:
                               type: object
+                            sqs:
+                              properties:
+                                awsRegion:
+                                  type: string
+                                queueName:
+                                  type: string
+                                queueOwnerAWSAccountID:
+                                  type: string
+                              required:
+                              - awsRegion
+                              - queueName
+                              - queueOwnerAWSAccountID
+                              type: object
                             udsink:
                               properties:
                                 container:
@@ -18826,9 +18871,15 @@ spec:
                           properties:
                             backoff:
                               properties:
+                                cap:
+                                  type: string
+                                factor:
+                                  type: number
                                 interval:
                                   default: 1ms
                                   type: string
+                                jitter:
+                                  type: number
                                 steps:
                                   format: int32
                                   type: integer
@@ -18839,6 +18890,19 @@ spec:
                           type: object
                         serve:
                           type: object
+                        sqs:
+                          properties:
+                            awsRegion:
+                              type: string
+                            queueName:
+                              type: string
+                            queueOwnerAWSAccountID:
+                              type: string
+                          required:
+                          - awsRegion
+                          - queueName
+                          - queueOwnerAWSAccountID
+                          type: object
                         udsink:
                           properties:
                             container:
@@ -30015,6 +30079,19 @@ spec:
                                   type: object
                                 serve:
                                   type: object
+                                sqs:
+                                  properties:
+                                    awsRegion:
+                                      type: string
+                                    queueName:
+                                      type: string
+                                    queueOwnerAWSAccountID:
+                                      type: string
+                                  required:
+                                  - awsRegion
+                                  - queueName
+                                  - queueOwnerAWSAccountID
+                                  type: object
                                 udsink:
                                   properties:
                                     container:
@@ -30589,9 +30666,15 @@ spec:
                               properties:
                                 backoff:
                                   properties:
+                                    cap:
+                                      type: string
+                                    factor:
+                                      type: number
                                     interval:
                                       default: 1ms
                                       type: string
+                                    jitter:
+                                      type: number
                                     steps:
                                       format: int32
                                       type: integer
@@ -30602,6 +30685,19 @@ spec:
                               type: object
                             serve:
                               type: object
+                            sqs:
+                              properties:
+                                awsRegion:
+                                  type: string
+                                queueName:
+                                  type: string
+                                queueOwnerAWSAccountID:
+                                  type: string
+                              required:
+                              - awsRegion
+                              - queueName
+                              - queueOwnerAWSAccountID
+                              type: object
                             udsink:
                               properties:
                                 container:
@@ -37740,6 +37836,19 @@ spec:
                         type: object
                       serve:
                         type: object
+                      sqs:
+                        properties:
+                          awsRegion:
+                            type: string
+                          queueName:
+                            type: string
+                          queueOwnerAWSAccountID:
+                            type: string
+                        required:
+                        - awsRegion
+                        - queueName
+                        - queueOwnerAWSAccountID
+                        type: object
                       udsink:
                         properties:
                           container:
@@ -38314,9 +38423,15 @@ spec:
                     properties:
                       backoff:
                         properties:
+                          cap:
+                            type: string
+                          factor:
+                            type: number
                           interval:
                             default: 1ms
                             type: string
+                          jitter:
+                            type: number
                           steps:
                             format: int32
                             type: integer
@@ -38327,6 +38442,19 @@ spec:
                     type: object
                   serve:
                     type: object
+                  sqs:
+                    properties:
+                      awsRegion:
+                        type: string
+                      queueName:
+                        type: string
+                      queueOwnerAWSAccountID:
+                        type: string
+                    required:
+                    - awsRegion
+                    - queueName
+                    - queueOwnerAWSAccountID
+                    type: object
                   udsink:
                     properties:
                       container:
diff --git a/config/namespace-install.yaml b/config/namespace-install.yaml
index 0cf4e4b5..8c039382 100644
--- a/config/namespace-install.yaml
+++ b/config/namespace-install.yaml
@@ -6864,6 +6864,19 @@ spec:
                         type: object
                       serve:
                         type: object
+                      sqs:
+                        properties:
+                          awsRegion:
+                            type: string
+                          queueName:
+                            type: string
+                          queueOwnerAWSAccountID:
+                            type: string
+                        required:
+                        - awsRegion
+                        - queueName
+                        - queueOwnerAWSAccountID
+                        type: object
                       udsink:
                         properties:
                           container:
@@ -7438,9 +7451,15 @@ spec:
                     properties:
                       backoff:
                         properties:
+                          cap:
+                            type: string
+                          factor:
+                            type: number
                           interval:
                             default: 1ms
                             type: string
+                          jitter:
+                            type: number
                           steps:
                             format: int32
                             type: integer
@@ -7451,6 +7470,19 @@ spec:
                     type: object
                   serve:
                     type: object
+                  sqs:
+                    properties:
+                      awsRegion:
+                        type: string
+                      queueName:
+                        type: string
+                      queueOwnerAWSAccountID:
+                        type: string
+                    required:
+                    - awsRegion
+                    - queueName
+                    - queueOwnerAWSAccountID
+                    type: object
                   udsink:
                     properties:
                       container:
@@ -18252,6 +18284,19 @@ spec:
                               type: object
                             serve:
                               type: object
+                            sqs:
+                              properties:
+                                awsRegion:
+                                  type: string
+                                queueName:
+                                  type: string
+                                queueOwnerAWSAccountID:
+                                  type: string
+                              required:
+                              - awsRegion
+                              - queueName
+                              - queueOwnerAWSAccountID
+                              type: object
                             udsink:
                               properties:
                                 container:
@@ -18826,9 +18871,15 @@ spec:
                           properties:
                             backoff:
                               properties:
+                                cap:
+                                  type: string
+                                factor:
+                                  type: number
                                 interval:
                                   default: 1ms
                                   type: string
+                                jitter:
+                                  type: number
                                 steps:
                                   format: int32
                                   type: integer
@@ -18839,6 +18890,19 @@ spec:
                           type: object
                         serve:
                           type: object
+                        sqs:
+                          properties:
+                            awsRegion:
+                              type: string
+                            queueName:
+                              type: string
+                            queueOwnerAWSAccountID:
+                              type: string
+                          required:
+                          - awsRegion
+                          - queueName
+                          - queueOwnerAWSAccountID
+                          type: object
                         udsink:
                           properties:
                             container:
@@ -30015,6 +30079,19 @@ spec:
                                   type: object
                                 serve:
                                   type: object
+                                sqs:
+                                  properties:
+                                    awsRegion:
+                                      type: string
+                                    queueName:
+                                      type: string
+                                    queueOwnerAWSAccountID:
+                                      type: string
+                                  required:
+                                  - awsRegion
+                                  - queueName
+                                  - queueOwnerAWSAccountID
+                                  type: object
                                 udsink:
                                   properties:
                                     container:
@@ -30589,9 +30666,15 @@ spec:
                               properties:
                                 backoff:
                                   properties:
+                                    cap:
+                                      type: string
+                                    factor:
+                                      type: number
                                     interval:
                                       default: 1ms
                                       type: string
+                                    jitter:
+                                      type: number
                                     steps:
                                       format: int32
                                       type: integer
@@ -30602,6 +30685,19 @@ spec:
                               type: object
                             serve:
                               type: object
+                            sqs:
+                              properties:
+                                awsRegion:
+                                  type: string
+                                queueName:
+                                  type: string
+                                queueOwnerAWSAccountID:
+                                  type: string
+                              required:
+                              - awsRegion
+                              - queueName
+                              - queueOwnerAWSAccountID
+                              type: object
                             udsink:
                               properties:
                                 container:
@@ -37740,6 +37836,19 @@ spec:
                         type: object
                       serve:
                         type: object
+                      sqs:
+                        properties:
+                          awsRegion:
+                            type: string
+                          queueName:
+                            type: string
+                          queueOwnerAWSAccountID:
+                            type: string
+                        required:
+                        - awsRegion
+                        - queueName
+                        - queueOwnerAWSAccountID
+                        type: object
                       udsink:
                         properties:
                           container:
@@ -38314,9 +38423,15 @@ spec:
                     properties:
                       backoff:
                         properties:
+                          cap:
+                            type: string
+                          factor:
+                            type: number
                           interval:
                             default: 1ms
                             type: string
+                          jitter:
+                            type: number
                           steps:
                             format: int32
                             type: integer
@@ -38327,6 +38442,19 @@ spec:
                     type: object
                   serve:
                     type: object
+                  sqs:
+                    properties:
+                      awsRegion:
+                        type: string
+                      queueName:
+                        type: string
+                      queueOwnerAWSAccountID:
+                        type: string
+                    required:
+                    - awsRegion
+                    - queueName
+                    - queueOwnerAWSAccountID
+                    type: object
                   udsink:
                     properties:
                       container:
diff --git a/docs/APIs.md b/docs/APIs.md
index 27987c3d..80f934ca 100644
--- a/docs/APIs.md
+++ b/docs/APIs.md
@@ -536,6 +536,26 @@ ServingPipeline.
 
 </tr>
 
+<tr>
+
+<td>
+
+<code>sqs</code></br> <em>
+<a href="#numaflow.numaproj.io/v1alpha1.SqsSink"> SqsSink </a> </em>
+</td>
+
+<td>
+
+<em>(Optional)</em>
+<p>
+
+SQS sink is used to write the data to the AWS SQS.
+</p>
+
+</td>
+
+</tr>
+
 </tbody>
 
 </table>
@@ -1079,7 +1099,7 @@ Kubernetes meta/v1.Duration </a> </em>
 <em>(Optional)</em>
 <p>
 
-Interval sets the delay to wait before retry, after a failure occurs.
+Interval sets the initial retry duration, after a failure occurs.
 </p>
 
 </td>
@@ -1098,8 +1118,71 @@ Interval sets the delay to wait before retry, after a failure occurs.
 <em>(Optional)</em>
 <p>
 
-Steps defines the number of times to try writing to a sink including
-retries
+Steps defines the maximum number of retry attempts
+</p>
+
+</td>
+
+</tr>
+
+<tr>
+
+<td>
+
+<code>factor</code></br> <em> float64 </em>
+</td>
+
+<td>
+
+<em>(Optional)</em>
+<p>
+
+Interval is multiplied by factor each iteration, if factor is not zero
+and the limits imposed by Steps and Cap have not been reached.
+</p>
+
+</td>
+
+</tr>
+
+<tr>
+
+<td>
+
+<code>cap</code></br> <em>
+<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Duration">
+Kubernetes meta/v1.Duration </a> </em>
+</td>
+
+<td>
+
+<em>(Optional)</em>
+<p>
+
+A limit on revised values of the interval parameter. If a multiplication
+by the factor parameter would make the interval exceed the cap then the
+interval is set to the cap and the steps parameter is set to zero.
+</p>
+
+</td>
+
+</tr>
+
+<tr>
+
+<td>
+
+<code>jitter</code></br> <em> float64 </em>
+</td>
+
+<td>
+
+<em>(Optional)</em>
+<p>
+
+The sleep at each iteration is the interval plus an additional amount
+chosen uniformly at random from the interval between zero and
+<code>jitter\*interval</code>.
 </p>
 
 </td>
@@ -9218,9 +9301,10 @@ RetryStrategy
 
 <p>
 
-RetryStrategy struct encapsulates the settings for retrying operations
-in the event of failures. It includes a BackOff strategy to manage the
-timing of retries and defines the action to take upon failure.
+The RetryStrategy struct defines the configuration for handling
+operation retries in case of failures. It incorporates an Exponential
+BackOff strategy to control retry timing and specifies the actions to
+take upon failure.
 </p>
 
 </p>
@@ -9260,8 +9344,8 @@ Description
 <em>(Optional)</em>
 <p>
 
-BackOff specifies the parameters for the backoff strategy, controlling
-how delays between retries should increase.
+BackOff specifies the parameters for the exponential backoff strategy,
+controlling how delays between retries should increase.
 </p>
 
 </td>
@@ -11428,6 +11512,101 @@ JetStreamSource </a> </em>
 
 </table>
 
+<h3 id="numaflow.numaproj.io/v1alpha1.SqsSink">
+
+SqsSink
+</h3>
+
+<p>
+
+(<em>Appears on:</em>
+<a href="#numaflow.numaproj.io/v1alpha1.AbstractSink">AbstractSink</a>)
+</p>
+
+<p>
+
+</p>
+
+<table>
+
+<thead>
+
+<tr>
+
+<th>
+
+Field
+</th>
+
+<th>
+
+Description
+</th>
+
+</tr>
+
+</thead>
+
+<tbody>
+
+<tr>
+
+<td>
+
+<code>awsRegion</code></br> <em> string </em>
+</td>
+
+<td>
+
+<p>
+
+AWSRegion is the AWS Region where the SQS queue is located
+</p>
+
+</td>
+
+</tr>
+
+<tr>
+
+<td>
+
+<code>queueName</code></br> <em> string </em>
+</td>
+
+<td>
+
+<p>
+
+QueueName is the name of the SQS queue
+</p>
+
+</td>
+
+</tr>
+
+<tr>
+
+<td>
+
+<code>queueOwnerAWSAccountID</code></br> <em> string </em>
+</td>
+
+<td>
+
+<p>
+
+QueueOwnerAWSAccountID is the queue owner aws account id
+</p>
+
+</td>
+
+</tr>
+
+</tbody>
+
+</table>
+
 <h3 id="numaflow.numaproj.io/v1alpha1.SqsSource">
 
 SqsSource
diff --git a/docs/assets/errors/errors-architecture.png b/docs/assets/errors/errors-architecture.png
new file mode 100644
index 00000000..6c579994
Binary files /dev/null and b/docs/assets/errors/errors-architecture.png differ
diff --git a/docs/assets/errors/ui-errors-tab.png b/docs/assets/errors/ui-errors-tab.png
new file mode 100644
index 00000000..c6dbc754
Binary files /dev/null and b/docs/assets/errors/ui-errors-tab.png differ
diff --git a/docs/assets/logs/logs-add-timestamps.png b/docs/assets/logs/logs-add-timestamps.png
new file mode 100644
index 00000000..54432077
Binary files /dev/null and b/docs/assets/logs/logs-add-timestamps.png differ
diff --git a/docs/assets/logs/logs-ascending.png b/docs/assets/logs/logs-ascending.png
new file mode 100644
index 00000000..dadd820e
Binary files /dev/null and b/docs/assets/logs/logs-ascending.png differ
diff --git a/docs/assets/logs/logs-dark-mode.png b/docs/assets/logs/logs-dark-mode.png
new file mode 100644
index 00000000..f482f80e
Binary files /dev/null and b/docs/assets/logs/logs-dark-mode.png differ
diff --git a/docs/assets/logs/logs-filter-error.png b/docs/assets/logs/logs-filter-error.png
new file mode 100644
index 00000000..a3ef1fc8
Binary files /dev/null and b/docs/assets/logs/logs-filter-error.png differ
diff --git a/docs/assets/logs/logs-negate-search.png b/docs/assets/logs/logs-negate-search.png
new file mode 100644
index 00000000..603788b9
Binary files /dev/null and b/docs/assets/logs/logs-negate-search.png differ
diff --git a/docs/assets/logs/logs-pause.png b/docs/assets/logs/logs-pause.png
new file mode 100644
index 00000000..744c38b5
Binary files /dev/null and b/docs/assets/logs/logs-pause.png differ
diff --git a/docs/assets/logs/logs-search.png b/docs/assets/logs/logs-search.png
new file mode 100644
index 00000000..dc43c450
Binary files /dev/null and b/docs/assets/logs/logs-search.png differ
diff --git a/docs/assets/logs/logs-tab.png b/docs/assets/logs/logs-tab.png
new file mode 100644
index 00000000..0aa71fab
Binary files /dev/null and b/docs/assets/logs/logs-tab.png differ
diff --git a/docs/assets/logs/logs-wrap.png b/docs/assets/logs/logs-wrap.png
new file mode 100644
index 00000000..b5dfde6a
Binary files /dev/null and b/docs/assets/logs/logs-wrap.png differ
diff --git a/docs/assets/logs/select-container.png b/docs/assets/logs/select-container.png
new file mode 100644
index 00000000..759d6b68
Binary files /dev/null and b/docs/assets/logs/select-container.png differ
diff --git a/docs/assets/logs/select-pod.png b/docs/assets/logs/select-pod.png
new file mode 100644
index 00000000..228b7fc0
Binary files /dev/null and b/docs/assets/logs/select-pod.png differ
diff --git a/docs/assets/metrics/dimensions.png b/docs/assets/metrics/dimensions.png
new file mode 100644
index 00000000..3da089fb
Binary files /dev/null and b/docs/assets/metrics/dimensions.png differ
diff --git a/docs/assets/metrics/metrics-tab.png b/docs/assets/metrics/metrics-tab.png
new file mode 100644
index 00000000..edf56fea
Binary files /dev/null and b/docs/assets/metrics/metrics-tab.png differ
diff --git a/docs/assets/metrics/mvtx-histogram.png b/docs/assets/metrics/mvtx-histogram.png
new file mode 100644
index 00000000..c8990fe3
Binary files /dev/null and b/docs/assets/metrics/mvtx-histogram.png differ
diff --git a/docs/assets/metrics/quantiles.png b/docs/assets/metrics/quantiles.png
new file mode 100644
index 00000000..f62f7981
Binary files /dev/null and b/docs/assets/metrics/quantiles.png differ
diff --git a/docs/assets/metrics/query-window.png b/docs/assets/metrics/query-window.png
new file mode 100644
index 00000000..c9f8435f
Binary files /dev/null and b/docs/assets/metrics/query-window.png differ
diff --git a/docs/assets/metrics/time-range.png b/docs/assets/metrics/time-range.png
new file mode 100644
index 00000000..d45a2568
Binary files /dev/null and b/docs/assets/metrics/time-range.png differ
diff --git a/docs/assets/numaflow-ui-advanced-pipeline.png b/docs/assets/numaflow-ui-advanced-pipeline.png
deleted file mode 100644
index 3583ed83..00000000
Binary files a/docs/assets/numaflow-ui-advanced-pipeline.png and /dev/null differ
diff --git a/docs/assets/numaflow-ui-simple-pipeline.png b/docs/assets/numaflow-ui-simple-pipeline.png
deleted file mode 100644
index 69404669..00000000
Binary files a/docs/assets/numaflow-ui-simple-pipeline.png and /dev/null differ
diff --git a/docs/assets/quick-start/cluster-view.png b/docs/assets/quick-start/cluster-view.png
new file mode 100644
index 00000000..646ea2ea
Binary files /dev/null and b/docs/assets/quick-start/cluster-view.png differ
diff --git a/docs/assets/quick-start/default-namespace-view.png b/docs/assets/quick-start/default-namespace-view.png
new file mode 100644
index 00000000..c6ba319f
Binary files /dev/null and b/docs/assets/quick-start/default-namespace-view.png differ
diff --git a/docs/assets/quick-start/ui-advanced-pipeline.png b/docs/assets/quick-start/ui-advanced-pipeline.png
new file mode 100644
index 00000000..45c2fa24
Binary files /dev/null and b/docs/assets/quick-start/ui-advanced-pipeline.png differ
diff --git a/docs/assets/quick-start/ui-simple-monovertex.png b/docs/assets/quick-start/ui-simple-monovertex.png
new file mode 100644
index 00000000..2a245886
Binary files /dev/null and b/docs/assets/quick-start/ui-simple-monovertex.png differ
diff --git a/docs/assets/quick-start/ui-simple-pipeline.png b/docs/assets/quick-start/ui-simple-pipeline.png
new file mode 100644
index 00000000..31143401
Binary files /dev/null and b/docs/assets/quick-start/ui-simple-pipeline.png differ
diff --git a/docs/assets/ui-overview/buffer-info.png b/docs/assets/ui-overview/buffer-info.png
new file mode 100644
index 00000000..117628a8
Binary files /dev/null and b/docs/assets/ui-overview/buffer-info.png differ
diff --git a/docs/assets/ui-overview/k8s-events.png b/docs/assets/ui-overview/k8s-events.png
new file mode 100644
index 00000000..385ca15b
Binary files /dev/null and b/docs/assets/ui-overview/k8s-events.png differ
diff --git a/docs/assets/ui-overview/processing-rates.png b/docs/assets/ui-overview/processing-rates.png
new file mode 100644
index 00000000..e08ec6d9
Binary files /dev/null and b/docs/assets/ui-overview/processing-rates.png differ
diff --git a/docs/assets/ui-overview/spec-view.png b/docs/assets/ui-overview/spec-view.png
new file mode 100644
index 00000000..8eaabd8c
Binary files /dev/null and b/docs/assets/ui-overview/spec-view.png differ
diff --git a/docs/assets/ui-overview/vertex-view.png b/docs/assets/ui-overview/vertex-view.png
new file mode 100644
index 00000000..385f480c
Binary files /dev/null and b/docs/assets/ui-overview/vertex-view.png differ
diff --git a/docs/operations/metrics/metrics.md b/docs/operations/metrics/metrics.md
index 125cf0da..d00543fa 100644
--- a/docs/operations/metrics/metrics.md
+++ b/docs/operations/metrics/metrics.md
@@ -32,20 +32,21 @@ These metrics can be used to determine throughput of your pipeline.
 
 These metrics can be used to determine the latency of your pipeline.
 
-| Metric name                                    | Metric type | Labels                                                                                                                                                        | Description                                                                                                 |
-| ---------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
-| `pipeline_processing_lag`                      | Gauge       | `pipeline=<pipeline-name>`                                                                                                                                    | Pipeline processing lag in milliseconds (max watermark - min watermark)                                     |
-| `pipeline_watermark_cmp_now`                   | Gauge       | `pipeline=<pipeline-name>`                                                                                                                                    | Max watermark of source compared with current time in milliseconds                                          |
-| `forwarder_read_processing_time`               | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of read operations                              |
-| `forwarder_write_processing_time`              | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of write operations                             |
-| `forwarder_ack_processing_time`                | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of ack operations                               |
-| `forwarder_fbsink_write_processing_time`       | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of write operations to a fallback sink          |
-| `source_forwarder_transformer_processing_time` | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=Source` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>`        | Provides a histogram distribution of the processing times of source transformer                             |
-| `forwarder_udf_processing_time`                | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>`                                        | Provides a histogram distribution of the processing times of User-defined Functions. (UDF's)                |
-| `forwarder_forward_chunk_processing_time`      | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>`                                        | Provides a histogram distribution of the processing times of the forwarder function as a whole              |
-| `reduce_pnf_process_time`                      | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `replica=<replica-index>`                                                                         | Provides a histogram distribution of the processing times of the reducer                                    |
-| `reduce_pnf_forward_time`                      | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `replica=<replica-index>`                                                                         | Provides a histogram distribution of the forwarding times of the reducer                                    |
-| `vertex_pending_messages`                      | Gauge       | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `period=<duration>` <br> `partition_name=<partition-name>`                                        | Provides the average pending messages in the last period of seconds. It is the pending messages of a vertex |
+| Metric name                                    | Metric type | Labels                                                                                                                                                        | Description                                                                                                    |
+| ---------------------------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
+| `pipeline_processing_lag`                      | Gauge       | `pipeline=<pipeline-name>`                                                                                                                                    | Pipeline processing lag in milliseconds (max watermark - min watermark)                                        |
+| `pipeline_watermark_cmp_now`                   | Gauge       | `pipeline=<pipeline-name>`                                                                                                                                    | Max watermark of source compared with current time in milliseconds                                             |
+| `forwarder_read_processing_time`               | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of read operations                                 |
+| `forwarder_write_processing_time`              | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of write operations                                |
+| `forwarder_ack_processing_time`                | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of ack operations                                  |
+| `forwarder_fbsink_write_processing_time`       | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>` | Provides the histogram distribution of the processing times of write operations to a fallback sink             |
+| `source_forwarder_transformer_processing_time` | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=Source` <br> `replica=<replica-index>` <br> `partition_name=<partition-name>`        | Provides a histogram distribution of the processing times of source transformer                                |
+| `forwarder_udf_processing_time`                | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>`                                        | Provides a histogram distribution of the processing times of User-defined Functions (UDFs) in a map vertex     |
+| `forwarder_forward_chunk_processing_time`      | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `vertex_type=<vertex-type>` <br> `replica=<replica-index>`                                        | Provides a histogram distribution of the processing times of the forwarder function as a whole in a map vertex |
+| `reduce_data_forward_forward_time`             | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `replica=<replica-index>`                                                                         | Provides a histogram distribution of the forwarding times of the data from an ISB to a PBQ                     |
+| `reduce_pbq_write_time`                        | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `replica=<replica-index>`                                                                         | Provides a histogram distribution of the processing times of write operations to a PBQ                         |
+| `reduce_pnf_process_time`                      | Histogram   | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `replica=<replica-index>`                                                                         | Provides a histogram distribution of the processing times of the reducer                                       |
+| `vertex_pending_messages`                      | Gauge       | `pipeline=<pipeline-name>` <br> `vertex=<vertex-name>` <br> `period=<duration>` <br> `partition_name=<partition-name>`                                        | Provides the average pending messages in the last period of seconds. It is the pending messages of a vertex    |
 
 ### Errors
 
@@ -115,7 +116,7 @@ You can follow the [prometheus operator](https://github.com/prometheus-operator/
 
 You can also set up prometheus operator via [helm](https://bitnami.com/stack/prometheus-operator/helm).
 
-### Configure the below Service/Pod Monitors for scraping your pipeline metrics:
+### Configure the below Service/Pod Monitors for scraping your pipeline/monovertex metrics:
 
 ```yaml
 apiVersion: monitoring.coreos.com/v1
diff --git a/docs/quick-start.md b/docs/quick-start.md
index 972730b1..9f529210 100644
--- a/docs/quick-start.md
+++ b/docs/quick-start.md
@@ -1,125 +1,161 @@
 # Quick Start
 
-In this page, we will guide you through the steps to:
+This guide will walk you through the following steps:
 
-1. Install Numaflow.
-2. Create and run a simple pipeline.
-3. Create and run an advanced pipeline.
+1. [Installing Numaflow](#installing-numaflow)
+2. [Creating and running a simple Numaflow Pipeline](#creating-a-simple-pipeline)
+3. [Creating and running an advanced Numaflow Pipeline](#creating-an-advanced-pipeline)
+4. [Creating and running a Numaflow MonoVertex](#creating-a-monovertex)
 
-## Before you begin: prerequisites
+## Before You Begin: Prerequisites
 
-To try Numaflow, you will first need to setup using one of the following options to run container images:
+To get started with Numaflow, ensure you have the following tools and setups ready:
+
+### Container Runtime
+
+You need a container runtime to run container images. Choose one of the following options:
 
 - [Docker Desktop](https://docs.docker.com/get-docker/)
-- [podman](https://podman.io/)
+- [Podman](https://podman.io/)
+
+### Local Kubernetes Cluster
 
-Then use one of the following options to create a local Kubernetes cluster:
+Set up a local Kubernetes cluster using one of these tools:
 
 - [Docker Desktop Kubernetes](https://docs.docker.com/desktop/kubernetes/)
 - [k3d](https://k3d.io/)
 - [kind](https://kind.sigs.k8s.io/)
 - [minikube](https://minikube.sigs.k8s.io/docs/start/)
 
-You will also need `kubectl` to manage the cluster. [Follow these steps to install kubectl](https://kubernetes.io/docs/tasks/tools/install-kubectl/). In case you need a refresher, all the `kubectl` commands used in this quick start guide can be found in the [kubectl Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/).
+### Kubernetes CLI (`kubectl`)
+
+Install `kubectl` to manage your Kubernetes cluster. Follow the [official guide](https://kubernetes.io/docs/tasks/tools/#kubectl) for installation instructions. If you're unfamiliar with `kubectl`, refer to the [kubectl Quick Reference Page](https://kubernetes.io/docs/reference/kubectl/quick-reference/) for a list of commonly used commands.
+
+Once these prerequisites are in place, you're ready to proceed with installing and using Numaflow.
 
 ## Installing Numaflow
 
-Once you have completed all the prerequisites, run the following command lines to install Numaflow and start the [Inter-Step Buffer Service](./core-concepts/inter-step-buffer-service.md) that handles communication between vertices.
+After completing the prerequisites, follow these steps to install Numaflow and set up the [Inter-Step Buffer Service](./core-concepts/inter-step-buffer-service.md), which facilitates communication between pipeline vertices:
+
+### Create a namespace for Numaflow
 
 ```shell
 kubectl create ns numaflow-system
+```
+
+### Install Numaflow components
+
+```shell
 kubectl apply -n numaflow-system -f https://raw.githubusercontent.com/numaproj/numaflow/main/config/install.yaml
+```
+
+### Deploy the JetStream-based Inter-Step Buffer Service
+
+```shell
 kubectl apply -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/0-isbsvc-jetstream.yaml
 ```
 
-## Creating a simple pipeline
+Once these steps are complete, Numaflow will be ready for use.
+
+## Creating a Simple Pipeline
 
-As an example, we will create a `simple pipeline` that contains a source vertex to generate messages, a processing vertex that echos the messages, and a sink vertex that logs the messages.
+In this section, we will create a `simple pipeline` that includes a source vertex to generate messages, a processing vertex that echoes the messages, and a sink vertex to log the messages.
 
-Run the command below to create a simple pipeline.
+### Deploy the Simple Pipeline
+
+Run the following command to deploy the simple pipeline:
 
 ```shell
 kubectl apply -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/1-simple-pipeline.yaml
 ```
 
-To view a list of pipelines you've created, run:
+### Verify the Pipeline Deployment
+
+To view the list of pipelines you have created, use the following command:
 
 ```shell
 kubectl get pipeline # or "pl" as a short name
 ```
 
-This should create a response like the following, with `AGE` indicating the time elapsed since the creation of your simple pipeline.
+You should see an output similar to this, where `AGE` indicates the time elapsed since the pipeline was created:
 
 ```shell
-NAME              PHASE     MESSAGE   VERTICES   AGE
-simple-pipeline   Running             3          9s
+NAME              PHASE     VERTICES   AGE   MESSAGE
+simple-pipeline   Running   3          47s
 ```
 
-To inspect the status of the pipeline, use `kubectl get pods`. Note that the pod names will be different from the sample response:
+Next, inspect the status of the pipeline by checking the pods. Note that the pod names in your environment may differ from the example below:
 
 ```shell
 # Wait for pods to be ready
 kubectl get pods
 
-NAME                                         READY   STATUS      RESTARTS   AGE
-isbsvc-default-js-0                          3/3     Running     0          19s
-isbsvc-default-js-1                          3/3     Running     0          19s
-isbsvc-default-js-2                          3/3     Running     0          19s
-simple-pipeline-daemon-78b798fb98-qf4t4      1/1     Running     0          10s
-simple-pipeline-out-0-xc0pf                  1/1     Running     0          10s
-simple-pipeline-cat-0-kqrhy                  2/2     Running     0          10s
-simple-pipeline-in-0-rhpjm                   1/1     Running     0          11s
+NAME                                         READY   STATUS      RESTARTS       AGE
+isbsvc-default-js-0                          3/3     Running        0           4m9s
+isbsvc-default-js-1                          3/3     Running        0           4m9s
+isbsvc-default-js-2                          3/3     Running        0           4m9s
+simple-pipeline-cat-0-xjqbe                  3/3     Running        0           99s
+simple-pipeline-daemon-784d5cfd97-vpsmk      1/1     Running        0           99s
+simple-pipeline-in-0-vvhu1                   2/2     Running        0           100s
+simple-pipeline-out-0-y1z8e                  2/2     Running        0           99s
 ```
 
-Now you can watch the log for the `output` vertex. Run the command below and remember to replace `xxxxx` with the appropriate pod name above.
+### View Logs for the Output Vertex
+
+To monitor the logs for the `output` vertex, run the following command. Replace `xxxxx` with the appropriate pod name from the output above:
 
 ```shell
 kubectl logs -f simple-pipeline-out-0-xxxxx
 ```
 
-This should generate an output like the sample below:
+You should see logs similar to the following:
 
 ```shell
-2022/08/25 23:59:38 (out) {"Data":"VT+G+/W7Dhc=","Createdts":1661471977707552597}
-2022/08/25 23:59:38 (out) {"Data":"0TaH+/W7Dhc=","Createdts":1661471977707615953}
-2022/08/25 23:59:38 (out) {"Data":"EEGH+/W7Dhc=","Createdts":1661471977707618576}
-2022/08/25 23:59:38 (out) {"Data":"WESH+/W7Dhc=","Createdts":1661471977707619416}
-2022/08/25 23:59:38 (out) {"Data":"YEaH+/W7Dhc=","Createdts":1661471977707619936}
-2022/08/25 23:59:39 (out) {"Data":"qfomN/a7Dhc=","Createdts":1661471978707942057}
-2022/08/25 23:59:39 (out) {"Data":"aUcnN/a7Dhc=","Createdts":1661471978707961705}
-2022/08/25 23:59:39 (out) {"Data":"iUonN/a7Dhc=","Createdts":1661471978707962505}
-2022/08/25 23:59:39 (out) {"Data":"mkwnN/a7Dhc=","Createdts":1661471978707963034}
-2022/08/25 23:59:39 (out) {"Data":"jk4nN/a7Dhc=","Createdts":1661471978707963534}
+2025/05/09 11:23:38 (out)  Payload -  {"Data":{"value":1746789818182898304},"Createdts":1746789818182898304}  Keys -  [key-0-0]  EventTime -  1746789818182  Headers -    ID -  cat-1526-0-0
+2025/05/09 11:23:38 (out)  Payload -  {"Data":{"value":1746789818182898304},"Createdts":1746789818182898304}  Keys -  [key-0-0]  EventTime -  1746789818182  Headers -    ID -  cat-1529-0-0
+2025/05/09 11:23:38 (out)  Payload -  {"Data":{"value":1746789818182898304},"Createdts":1746789818182898304}  Keys -  [key-0-0]  EventTime -  1746789818182  Headers -    ID -  cat-1528-0-0
 ```
 
-Numaflow also comes with a built-in user interface.
+### Access the Numaflow UI
 
-**NOTE**: Please install the metrics server if your local Kubernetes cluster does not bring it by default (e.g., Kind).
-You can install it by running the below command.
+Numaflow includes a built-in user interface for monitoring pipelines. If your local Kubernetes cluster does not include a metrics server by default (e.g., Kind), install it using the following commands:
 
 ```shell
 kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
 kubectl patch -n kube-system deployment metrics-server --type=json -p '[{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}]'
 ```
 
-To port forward the UI, run the following command.
+To access the UI, port-forward the Numaflow server:
 
 ```shell
-# Port forward the UI to https://localhost:8443/
 kubectl -n numaflow-system port-forward deployment/numaflow-server 8443:8443
 ```
 
-This renders the following UI on https://localhost:8443/.
+Visit https://localhost:8443/ to view the UI. Below is the UI for the `simple` pipeline:
+
+#### Cluster View
+
+![Cluster View](assets/quick-start/cluster-view.png)
+
+#### Default Namespace View
+
+![Default Namespace View](assets/quick-start/default-namespace-view.png)
+
+#### Simple Pipeline View
+
+![Simple Pipeline View](assets/quick-start/ui-simple-pipeline.png)
 
-![Numaflow UI](assets/numaflow-ui-simple-pipeline.png)
+> **Note**: For more details about the UI features and built-in debugging tools, check out the [UI section](./user-guide/UI/).
 
-The pipeline can be deleted by issuing the following command:
+### Deleting Pipeline
+
+To delete the simple pipeline, run the following command:
 
 ```shell
 kubectl delete -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/1-simple-pipeline.yaml
 ```
 
-## Creating an advanced pipeline
+## Creating an Advanced Pipeline
 
 Now we will walk you through creating an advanced pipeline. In our example, this is called the `even-odd` pipeline, illustrated by the following diagram:
 
@@ -127,68 +163,193 @@ Now we will walk you through creating an advanced pipeline. In our example, this
 
 There are five vertices in this example of an advanced pipeline. An [HTTP](./user-guide/sources/http.md) source vertex which serves an HTTP endpoint to receive numbers as source data, a [UDF](user-guide/user-defined-functions/map/map.md) vertex to tag the ingested numbers with the key `even` or `odd`, three [Log](./user-guide/sinks/log.md) sinks, one to print the `even` numbers, one to print the `odd` numbers, and the other one to print both the even and odd numbers.
 
-Run the following command to create the `even-odd` pipeline.
+### Deploying the `even-odd` Pipeline
+
+To deploy the `even-odd` pipeline, run the following command:
 
 ```shell
 kubectl apply -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/2-even-odd-pipeline.yaml
 ```
 
-You may opt to view the list of pipelines you've created so far by running `kubectl get pipeline`. Otherwise, proceed to inspect the status of the pipeline, using `kubectl get pods`.
+You can check the list of pipelines you have created so far using:
+
+```shell
+kubectl get pipeline # or "pl" as a short name
+```
+
+```shell
+NAME       PHASE     VERTICES   AGE   MESSAGE
+even-odd   Running   5          51s
+```
+
+Otherwise, proceed to inspect the status of the pipeline by checking the pods:
 
 ```shell
 # Wait for pods to be ready
 kubectl get pods
 
 NAME                               READY   STATUS    RESTARTS   AGE
-even-odd-daemon-64d65c945d-vjs9f   1/1     Running   0          5m3s
-even-odd-even-or-odd-0-pr4ze       2/2     Running   0          30s
-even-odd-even-sink-0-unffo         1/1     Running   0          22s
-even-odd-in-0-a7iyd                1/1     Running   0          5m3s
-even-odd-number-sink-0-zmg2p       1/1     Running   0          7s
-even-odd-odd-sink-0-2736r          1/1     Running   0          15s
-isbsvc-default-js-0                3/3     Running   0          10m
-isbsvc-default-js-1                3/3     Running   0          10m
-isbsvc-default-js-2                3/3     Running   0          10m
+even-odd-daemon-75cdcd5f4c-nmrrp   1/1     Running   0          95s
+even-odd-even-or-odd-0-i72hw       3/3     Running   0          95s
+even-odd-even-sink-0-gnhou         2/2     Running   0          95s
+even-odd-in-0-tvfef                2/2     Running   0          95s
+even-odd-number-sink-0-3s3nc       2/2     Running   0          95s
+even-odd-odd-sink-0-zktib          2/2     Running   0          95s
+isbsvc-default-js-0                3/3     Running   0          15m
+isbsvc-default-js-1                3/3     Running   0          15m
+isbsvc-default-js-2                3/3     Running   0          15m
 ```
 
-Next, port-forward the HTTP endpoint, and make a `POST` request using `curl`. Remember to replace `xxxxx` with the appropriate pod names both here and in the next step.
+### Sending Data to the Pipeline
+
+Port-forward the HTTP endpoint of the source vertex and send data using `curl`. Replace `xxxxx` with the appropriate pod name:
 
 ```shell
-kubectl port-forward even-odd-in-0-xxxx 8444:8443
+kubectl port-forward even-odd-in-0-xxxxx 8444:8443
 
-# Post data to the HTTP endpoint
+# Send data to the HTTP endpoint
 curl -kq -X POST -d "101" https://localhost:8444/vertices/in
 curl -kq -X POST -d "102" https://localhost:8444/vertices/in
 curl -kq -X POST -d "103" https://localhost:8444/vertices/in
 curl -kq -X POST -d "104" https://localhost:8444/vertices/in
 ```
 
-Now you can watch the log for the `even` and `odd` vertices by running the commands below.
+### Viewing Logs for the `even` and `odd` Vertices
+
+Monitor the logs for the `even` and `odd` vertices to verify the pipeline's functionality. Replace `xxxxx` with the appropriate pod names:
 
 ```shell
-# Watch the log for the even vertex
+# Logs for the even vertex
 kubectl logs -f even-odd-even-sink-0-xxxxx
-2022/09/07 22:29:40 (even-sink) 102
-2022/09/07 22:29:40 (even-sink) 104
 
-# Watch the log for the odd vertex
+2025/05/09 13:34:26 (even-sink)  Payload -  104  Keys -  [even]  EventTime -  1746797665477  Headers -  Content-Length: 3, Content-Type: application/x-www-form-urlencoded, User-Agent: curl/8.7.1, Accept: */*,   ID -  even-or-odd-1-0-0
+2025/05/09 13:34:26 (even-sink)  Payload -  102  Keys -  [even]  EventTime -  1746797665430  Headers -  Content-Type: application/x-www-form-urlencoded, User-Agent: curl/8.7.1, Accept: */*, Content-Length: 3,   ID -  even-or-odd-2-0-0
+
+# Logs for the odd vertex
 kubectl logs -f even-odd-odd-sink-0-xxxxx
-2022/09/07 22:30:19 (odd-sink) 101
-2022/09/07 22:30:19 (odd-sink) 103
+
+
+2025/05/09 13:34:26 (odd-sink)  Payload -  101  Keys -  [odd]  EventTime -  1746797665407  Headers -  Content-Length: 3, Content-Type: application/x-www-form-urlencoded, User-Agent: curl/8.7.1, Accept: */*,   ID -  even-or-odd-4-0-0
+2025/05/09 13:34:26 (odd-sink)  Payload -  103  Keys -  [odd]  EventTime -  1746797665452  Headers -  Content-Length: 3, Content-Type: application/x-www-form-urlencoded, User-Agent: curl/8.7.1, Accept: */*,   ID -  even-or-odd-3-0-0
 ```
 
-View the UI for the advanced pipeline at https://localhost:8443/.
+### Accessing the Numaflow UI
 
-![Numaflow UI](assets/numaflow-ui-advanced-pipeline.png)
+To visualize the pipeline, access the Numaflow UI at https://localhost:8443/ after port forwarding. Below is the UI for the `even-odd` pipeline:
 
-The source code of the `even-odd` [user-defined function](user-guide/user-defined-functions/user-defined-functions.md) can be found [here](https://github.com/numaproj/numaflow-go/tree/main/pkg/mapper/examples/even_odd). You also can replace the [Log](./user-guide/sinks/log.md) Sink with some other sinks like [Kafka](./user-guide/sinks/kafka.md) to forward the data to Kafka topics.
+![Numaflow UI](assets/quick-start/ui-advanced-pipeline.png)
 
-The pipeline can be deleted by
+### Deleting the Pipeline
+
+To delete the `even-odd` pipeline, run:
 
 ```shell
 kubectl delete -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/2-even-odd-pipeline.yaml
 ```
 
+### Additional Notes
+
+The source code for the `even-odd` [user-defined function](user-guide/user-defined-functions/user-defined-functions.md) is available [here](https://github.com/numaproj/numaflow-go/tree/main/pkg/mapper/examples/even_odd). You can also replace the [Log](./user-guide/sinks/log.md) Sink with other sinks, such as [Kafka](./user-guide/sinks/kafka.md), to forward data to Kafka topics.
+
+## Creating a MonoVertex
+
+This section demonstrates how to create and deploy a simple [MonoVertex](core-concepts/monovertex.md) that includes a [User-Defined Source (UDSource)](user-guide/sources/user-defined-sources.md), a [Transformer](user-guide/sources/transformer/overview.md), and a [User-Defined Sink (UDSink)](user-guide/sinks/user-defined-sinks.md). The sink in this example is a log sink.
+
+### Deploying the MonoVertex
+
+To deploy the MonoVertex, run the following command:
+
+```shell
+kubectl apply -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/21-simple-mono-vertex.yaml
+```
+
+### Verifying the Deployment
+
+Check the list of deployed MonoVertices using:
+
+```shell
+kubectl get monovertex # or "mvtx" as a short name
+```
+
+You should see an output similar to this:
+
+```shell
+NAME                 PHASE     DESIRED   CURRENT   READY   AGE   REASON   MESSAGE
+simple-mono-vertex   Running   1         1         1       38s
+```
+
+Inspect the status of the MonoVertex by listing the pods. Replace `xxxxx` with the appropriate pod name from your environment:
+
+```shell
+# Wait for pods to be ready
+kubectl get pods
+
+NAME                                           READY   STATUS    RESTARTS   AGE
+simple-mono-vertex-mv-0-w7fmq                  5/5     Running   0          2m30s
+simple-mono-vertex-mv-daemon-55bff65db5-mk4g2  1/1     Running   0          2m30s
+```
+
+### Viewing Pod Details
+
+To ensure all containers (monitor, udsource, transformer, udsink, and numa) are running, describe the MonoVertex pod:
+
+```shell
+kubectl describe pod simple-mono-vertex-mv-0-xxxxx
+```
+
+### Monitoring Logs for the Sink Container
+
+To view logs from the `udsink` container, use the following command. Replace `xxxxx` with the appropriate pod name:
+
+```shell
+kubectl logs -f simple-mono-vertex-mv-0-xxxxx -c udsink
+```
+
+You should see output similar to:
+
+```shell
+107705990
+107705991
+107705992
+107705993
+107705994
+107705995
+107705996
+107705997
+107705998
+107705999
+```
+
+### Accessing the Numaflow UI
+
+To visualize the MonoVertex, access the Numaflow UI by port-forwarding the Numaflow server:
+
+```shell
+kubectl -n numaflow-system port-forward deployment/numaflow-server 8443:8443
+```
+
+Visit https://localhost:8443/ to view the UI. Below is an example of the UI for the MonoVertex:
+
+![Simple MonoVertex View](assets/quick-start/ui-simple-monovertex.png)
+
+For more details about the UI features and debugging tools, refer to the UI section.(TODO: add UI section link)
+
+### Deleting the MonoVertex
+
+To delete the MonoVertex, run:
+
+```shell
+kubectl delete -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/21-simple-mono-vertex.yaml
+```
+
+### Additional Notes
+
+The source code for the user-defined functions used in this MonoVertex is available here:
+
+- [UDSource](https://github.com/numaproj/numaflow-rs/blob/main/examples/simple-source/src/main.rs)
+- [Transformer](https://github.com/numaproj/numaflow-rs/blob/main/examples/source-transformer-now/src/main.rs)
+- [UDSink](https://github.com/numaproj/numaflow-rs/blob/main/examples/sink-log/src/main.rs)
+
 ## A pipeline with reduce (aggregation)
 
 To set up an example pipeline with the [Reduce UDF](user-guide/user-defined-functions/reduce/reduce.md), see [Reduce Examples](user-guide/user-defined-functions/reduce/examples.md).
diff --git a/docs/user-guide/FAQ.md b/docs/user-guide/FAQ.md
new file mode 100644
index 00000000..4816e0c7
--- /dev/null
+++ b/docs/user-guide/FAQ.md
@@ -0,0 +1,38 @@
+# Frequently Asked Questions
+
+Welcome to the Numaflow FAQ! Here you'll find answers to common questions about getting started, compatibility, troubleshooting, and more.
+
+---
+
+### 1. How do I get started with Numaflow?
+
+To get started, follow the [Quickstart Guide](../quick-start.md). It provides step-by-step instructions for setting up a basic Numaflow pipeline.
+
+---
+
+### 2. How can I check SDK compatibility with Numaflow versions?
+
+Refer to the compatibility matrix [here](../user-guide/sdks/compatibility.md) to ensure your SDK version works with your Numaflow deployment.
+
+---
+
+### 3. Where can I learn about the latest releases of Numaflow?
+
+-   Visit the [Numaflow Releases page](https://github.com/numaproj/numaflow/releases) for the latest updates.
+-   Join our [Slack channel](https://join.slack.com/t/numaproj/shared_invite/zt-19svuv47m-YKHhsQ~~KK9mBv1E7pNzfg) to stay up to date with announcements and community discussions.
+
+---
+
+### 4. I see `Server Info File not ready` log in the numa container. What does this mean?
+
+This message indicates that the server has not started yet. Common reasons include:
+
+-   The server was not started correctly in the `udcontainer`.
+-   The server is still initializing (for example, the application code is downloading cache files or performing setup tasks).
+
+### 5. What is `partitions` in Numaflow?
+
+The field `partitions` is the number of streams between the vertices. Larger the parition number, higher the TPS that vertex can take. Note that partitions are owned by the vertex reading the data, to create a
+multi-partitioed edge we need to configure the vertex reading the data to have multiple partitions.
+
+If you have additional questions, please refer to the [documentation](../README.md) or reach out on [Slack](https://join.slack.com/t/numaproj/shared_invite/zt-19svuv47m-YKHhsQ~~KK9mBv1E7pNzfg)!
diff --git a/docs/user-guide/UI/errors.md b/docs/user-guide/UI/errors.md
new file mode 100644
index 00000000..907d93d5
--- /dev/null
+++ b/docs/user-guide/UI/errors.md
@@ -0,0 +1,86 @@
+# Errors
+
+The **Numaflow UI** provides an **Errors Tab** that lists the recent errors (up to 10 per container) that may have occurred in the user-defined function (UDF) code. This feature helps users quickly identify and debug application errors.
+
+In this guide, we will simulate a panic in a transformer and demonstrate how the errors are displayed in the Errors Tab for a monovertex.
+
+> **Note**: The Errors Tab and its related functionality are currently available only for MonoVertex. Support for Pipelines is planned for future releases.
+
+---
+
+## High-Level Architecture
+
+![High-Level Architecture](../../assets/errors/errors-architecture.png)
+
+---
+
+## Simulating an Error in the Transformer
+
+To simulate an error, we will introduce a panic in the Go SDK for the transformer by modifying the `Transform` function. You can find the relevant code [here](https://github.com/numaproj/numaflow-go/blob/main/pkg/sourcetransformer/examples/assign_event_time/main.go).
+
+Below is the modified code snippet:
+
+```go
+// AssignEventTime is a source transformer that assigns event time to the message.
+type AssignEventTime struct {
+    counter uint64
+}
+
+func (a *AssignEventTime) Transform(ctx context.Context, keys []string, d sourcetransformer.Datum) sourcetransformer.Messages {
+    newCount := atomic.AddUint64(&a.counter, 1)
+    // Trigger a panic if the counter is a multiple of 5
+    if newCount%5 == 0 {
+        panic("Counter reached a multiple of 5")
+    }
+    // Update the message's event time to the current time
+    eventTime := time.Now()
+    // Log the action for testing purposes
+    log.Printf("AssignEventTime: Assigning event time %v to message %s", eventTime, string(d.Value()))
+    return sourcetransformer.MessagesBuilder().Append(sourcetransformer.NewMessage(d.Value(), eventTime).WithKeys(keys))
+}
+```
+
+In this example, the code triggers a panic whenever the counter reaches a multiple of 5. This simulates an error scenario that will be captured and displayed in the Errors Tab.
+
+---
+
+## The UI Errors Tab
+
+![Errors Tab](../../assets/errors/ui-errors-tab.png)
+
+The Errors Tab provides the following features:
+
+- **Error Count**: Displays the total number of errors across all pods and containers at the top level.
+- **Pod and Container Filters**: Allows users to filter errors by selecting a specific pod or container for more focused debugging.
+- **Tabulated Errors**: Errors are displayed in a tabular format with the following columns:
+
+    - `Pod Name`
+    - `Container`
+    - `Message`
+    - `Last Occurred`
+
+- **Details**: Expanding an error entry reveals a `Details` section that includes the **stack trace** of the error, providing deeper insights for debugging.
+
+---
+
+## Types of Errors Displayed in the Errors Tab
+
+The Errors Tab in the **Numaflow UI** captures and displays various types of errors that may occur during the execution of user-defined functions (UDFs). Below are the common categories of errors you can expect:
+
+### 1. Exceptions in User Code
+
+Any exception that occurs within the user-defined function (UDF) code will be captured and displayed in the Errors Tab. These errors typically indicate issues in the logic or implementation of the UDF.
+
+### 2. Partial Responses
+
+Errors may occur when the user code sends partial or incorrect responses. For example:
+
+-   Returning `null` instead of `Message.toDrop` in a UDSink when a message needs to be dropped.
+
+### 3. Critical Errors Persisted by the User
+
+Users can invoke the `PersistCriticalError` utility function to log critical errors in the `emptyDir` volume. This is useful for capturing severe issues that require immediate attention. Below are links to the implementations in different SDKs:
+
+-   [Go Implementation](https://github.com/numaproj/numaflow-go/blob/47460a1854b8f58a0e918056ef4d169949193ebe/pkg/errors/errors.go)
+-   [Java Implementation](https://github.com/numaproj/numaflow-java/blob/2aeaafff1c6dec7fd66e018b142ef6fe4ffcf0a9/src/main/java/io/numaproj/numaflow/errors/PersistCriticalError.java)
+-   [Python Implementation](https://github.com/numaproj/numaflow-python/blob/6ccd49ef09ad11eac9b0bb9a6b0f5517d858bea4/pynumaflow/errors/errors.py)
diff --git a/docs/user-guide/UI/logs.md b/docs/user-guide/UI/logs.md
new file mode 100644
index 00000000..39e43a97
--- /dev/null
+++ b/docs/user-guide/UI/logs.md
@@ -0,0 +1,110 @@
+# Logs View
+
+The **Logs View** allows users to inspect the logs of a specific container within a pod of a vertex. This guide will walk you through the Logs tab and its various features.
+
+---
+
+## Navigating to the Logs Tab
+
+**Select a Pod**
+
+Navigate to the `Pods View` tab after selecting the vertex and select a pod by name from the `Select a Pod by Name` dropdown.
+
+![Select Pod](../../assets/logs/select-pod.png)
+
+**Select a Container**
+
+Choose a container from the `Select a Container` section.
+
+![Select Container](../../assets/logs/select-container.png)
+
+**View Logs**
+
+Open the **Logs Tab** on the right to view the container logs.
+
+![Logs View](../../assets/logs/logs-tab.png)
+
+---
+
+## Features
+
+### 1. Previous Terminated Container Logs
+
+- Enable the checkbox to view logs from previously terminated containers.
+- This is particularly useful for debugging issues.
+
+---
+
+### 2. Search Logs
+
+- Filter logs by typing keywords in the **Search Logs** box.
+
+![Search Logs](../../assets/logs/logs-search.png)
+
+---
+
+### 3. Negate Search
+
+- Enable the **Negate Search** option to exclude logs matching the search keywords from the view.
+
+![Negate Search](../../assets/logs/logs-negate-search.png)
+
+---
+
+### 4. Wrap Lines
+
+- Use the **Wrap Lines** feature to avoid horizontal scrolling for long log lines, improving readability.
+
+![Wrap Lines](../../assets/logs/logs-wrap.png)
+
+---
+
+### 5. Pause Logs
+
+- Pause the log stream to inspect logs when there is a high volume of data.
+
+![Pause Logs](../../assets/logs/logs-pause.png)
+
+---
+
+### 6. Dark Mode
+
+- Toggle between **Dark Mode** and **Light Mode** for better visibility based on your preference.
+
+![Dark Mode](../../assets/logs/logs-dark-mode.png)
+
+---
+
+### 7. Ascending/Descending Order
+
+- Switch between ascending and descending order of log timestamps for easier navigation.
+
+![Ascending Order](../../assets/logs/logs-ascending.png)
+
+---
+
+### 8. Download Logs
+
+- Download the last 1000 logs for offline analysis.
+
+---
+
+### 9. Add/Remove Timestamps
+
+- Toggle timestamps in the logs based on your requirements.
+
+![Add Timestamps](../../assets/logs/logs-add-timestamps.png)
+
+---
+
+### 10. Level-Based Filtering
+
+- Filter logs by log levels such as:
+    - **Info**
+    - **Error**
+    - **Warn**
+    - **Debug**
+
+![Error Filter](../../assets/logs/logs-filter-error.png)
+
+---
diff --git a/docs/user-guide/UI/metrics-tab.md b/docs/user-guide/UI/metrics-tab.md
new file mode 100644
index 00000000..ded6d81a
--- /dev/null
+++ b/docs/user-guide/UI/metrics-tab.md
@@ -0,0 +1,251 @@
+# Metrics Tab
+
+Numaflow provides a comprehensive [set of Prometheus metrics](../../operations/metrics/metrics.md), enabling users to monitor their pipelines effectively and set up alerts as needed. This feature enhances the debugging experience by offering contextual insights directly within the UI. By leveraging these metrics, users can identify and resolve issues more efficiently, ensuring smoother pipeline operations.
+
+---
+
+## Prerequisites
+
+Before visualizing metrics in the UI, ensure the following requirements are met:
+
+- **Prometheus Server Setup**: A Prometheus server must be configured and running. Refer to the [Prometheus Operator Installation Guide](https://github.com/prometheus-operator/prometheus-operator/blob/main/Documentation/getting-started/installation.md) for detailed instructions.
+- **Service Monitors**: Configure Service/Pod Monitors for scraping pipeline/monovertex metrics. Refer to [this section](../../operations/metrics/metrics.md#configure-the-below-servicepod-monitors-for-scraping-your-pipelinemonovertex-metrics) for configuration details.
+- **Basic Knowledge of PromQL**: Familiarity with PromQL (Prometheus Query Language) is recommended to effectively query and interpret the metrics.
+
+Meeting these prerequisites will help you make the most of the metrics visualization and monitoring features in Numaflow.
+
+---
+
+## Metrics Configuration
+
+To visualize metrics in the UI, you need to configure them using a ConfigMap. Numaflow provides a default [ConfigMap](https://github.com/numaproj/numaflow/blob/main/config/base/numaflow-server/numaflow-server-metrics-proxy-config.yaml) that includes a variety of pre-configured metrics. These metrics cover essential pipeline operations and also include custom metrics, such as CPU and memory usage.
+
+You can customize this ConfigMap to add or modify metrics based on your specific requirements. For example, you can include additional Prometheus metrics relevant to your use case.
+
+Refer to the [Numaflow Metrics Documentation](../../operations/metrics/metrics.md) for more details on available metrics and their usage.
+
+---
+
+### Current Configuration Snippet
+
+Below is a snippet of the default ConfigMap configuration:
+
+```yaml
+apiVersion: v1
+kind: ConfigMap
+metadata:
+    name: numaflow-server-metrics-proxy-config
+data:
+    config.yaml: |
+        url: http://prometheus-operated.monitoring.svc.cluster.local:9090
+        patterns:
+          - name: vertex_gauge
+            objects:
+              - vertex
+            title: Vertex Gauge Metrics
+            description: This pattern represents the gauge metrics for a vertex across different dimensions.
+            expr: |
+              sum($metric_name{$filters}) by ($dimension, period)
+            params:
+              - name: start_time
+                required: false
+              - name: end_time
+                required: false
+            metrics:
+              - metric_name: vertex_pending_messages
+                display_name: Vertex Pending Messages
+                metric_description: Tracks the total number of messages waiting to be processed over varying time frames (e.g., 1min, 5min, 15min).
+                required_filters:
+                  - namespace
+                  - pipeline
+                  - vertex
+                dimensions:
+                  - name: pod
+                    filters:
+                      - name: pod
+                        required: false
+                      - name: period
+                        required: false
+                  - name: vertex
+                    filters:
+                      - name: period
+                        required: false
+```
+
+### Key Definitions
+
+Prometheus metrics are categorized into three main types: Histogram, Gauge, and Counter. The configuration provided above groups metrics of a similar type under a single pattern, making it more generic and easier to manage.
+
+1. **url:**
+   The `url` is a required field that specifies the Prometheus service endpoint to which the metrics proxy will connect. If the `url` is not set or is incorrectly configured, the Metrics Tab will not be displayed in the UI.
+
+    - **Example**: If you set up a local Prometheus operator using [Helm](https://bitnami.com/stack/prometheus-operator/helm), the `url` would look like this:
+        `http://my-release-kube-prometheus-prometheus.default.svc.cluster.local:9090`
+
+2. **patterns:**
+   A list of patterns that group metrics of a similar type.
+
+    - **name**: The name of the pattern.
+    - **objects**: Specifies the object type, which can either be `vertex` or `mono-vertex`. For pipelines, the object is `vertex`, and for MonoVertex, the object is `mono-vertex`.
+    - **title**: The title of the pattern.
+    - **description**: A description of the pattern.
+    - **expr**: The PromQL expression used to construct queries, with placeholders for dynamic values.
+    - **params**: Common parameters for all metrics within the pattern. These may include:
+
+        - **start_time**: The start time for the PromQL query (optional).
+        - **end_time**: The end time for the PromQL query (optional).
+        - **duration**: The query window (required for histograms).
+        - **quantile**: The quantile value (required for histograms). The quantiles can be 0.99, 0.95, 0.90, or 0.50.
+
+    - **metrics**:
+      A list of metrics defined within the pattern.
+        - **metric_name**: The actual name of the metric.
+        - **display_name**: A user-friendly name for the metric. Avoid editing this for existing metrics.
+        - **metric_description**: A detailed description of the metric, displayed as an info icon next to the metric name in the UI.
+        - **required_filters**: Filters that must be included in the PromQL request body.
+        - **dimensions**: Dimensions allow users to drill down into specific components, such as pods or containers, for more granular data.
+            - **name**: The name of the dimension (e.g., pod, vertex).
+            - **filters**: Filters applied to the data for a specific key.
+                - **name**: The name of the filter.
+                - **required**: If set to `true`, the filter is automatically added to `$filters`. If `false`, users can select a value for the filter key, which is then added to `$filters`.
+                - **expr**: (Optional) Overrides the top-level `expr` for a specific metric and dimension.
+
+This structured configuration ensures flexibility and ease of use when visualizing and analyzing metrics in the Numaflow UI.
+
+---
+
+## Explanation with an Example
+
+The above configuration might seem complex at first glance. Let’s break it down with an example using the out-of-the-box metric `monovtx_processing_time_bucket`.
+
+### Example Configuration
+
+```yaml
+- name: mono_vertex_histogram
+  objects:
+   - mono-vertex
+  title: MonoVertex Histogram Metrics
+  description: This pattern is for P99, P95, P90, and P50 quantiles for a mono-vertex across different dimensions.
+  expr: |
+   histogram_quantile($quantile, sum by($dimension,le) (rate($metric_name{$filters}[$duration])))
+  params:
+   - name: quantile
+    required: true
+   - name: duration
+    required: true
+   - name: start_time
+    required: false
+   - name: end_time
+    required: false
+  metrics:
+   - metric_name: monovtx_processing_time_bucket
+    display_name: MonoVertex Processing Time Latency
+    metric_description: This metric represents a histogram to keep track of the total time taken to forward a chunk of messages.
+    required_filters:
+      - namespace
+      - mvtx_name
+    dimensions:
+      - name: mono-vertex
+      - name: pod
+       filters:
+        - name: pod
+          required: false
+```
+
+### Key Points
+
+1. **Pattern Name**: `mono_vertex_histogram`
+   This indicates that the pattern includes histogram metrics for a mono-vertex.
+
+2. **Expression (`expr`)**
+   The expression calculates quantiles (e.g., P99, P95). Learn more about quantiles in Prometheus [here](https://prometheus.io/docs/practices/histograms/#quantiles).
+   Placeholders such as `quantile`, `dimension`, `metric_name`, `filters`, and `duration` are dynamically populated based on the configuration.
+
+3. **Parameters (`params`)**
+
+    - `quantile` and `duration` are required placeholders in the expression.
+    - `start_time` and `end_time` are optional parameters for defining the query duration.
+
+4. **Metric Name (`metric_name`)**
+   This forms the `$metric_name` placeholder in the expression.
+
+5. **Required Filters (`required_filters`)**
+   These filters populate the `$filters` placeholder in the expression.
+
+6. **Dimensions**
+
+    - For the `mono-vertex` dimension, no additional filters are applied.
+    - For the `pod` dimension, an additional filter (`pod`) is appended to the `$filters` placeholder.
+
+---
+
+### Example Expressions
+
+Below are examples of how the placeholders are replaced to form the final PromQL expressions:
+
+- **Dimension**: `mono-vertex`
+  **Quantile**: `0.99`
+  **Namespace**: `default`
+  **MonoVertex Name**: `simple-mono-vertex`
+  **Duration**: `5m`
+  **Resultant Expression**:
+
+  ```promql
+  histogram_quantile(0.99, sum by(mvtx_name,le) (rate(monovtx_processing_time_bucket{namespace="default",mvtx_name="simple-mono-vertex"}[5m])))
+  ```
+
+- **Dimension**: `pod`
+  **Quantile**: `0.99`
+  **Namespace**: `default`
+  **MonoVertex Name**: `simple-mono-vertex`
+  **Pod**: `simple-mono-vertex-mv-0-edj2s`
+  **Duration**: `1m`
+  **Resultant Expression**:
+
+  ```promql
+  histogram_quantile(0.99, sum by(pod,le) (rate(monovtx_processing_time_bucket{namespace="default",mvtx_name="simple-mono-vertex",pod="simple-mono-vertex-mv-0-edj2s"}[1m])))
+  ```
+
+This example demonstrates how the configuration translates into actionable PromQL queries, making it easier to understand and customize.
+
+## How the Metrics Tab Appears in the UI
+
+The **Metrics Tab** is located next to the [Logs Tab](./logs.md) in the [Pods View](./pods-view.md).
+
+![Metrics Tab](../../assets/metrics/metrics-tab.png)
+
+The following screenshots illustrate how the UI translates the discussed configuration into a visual representation:
+
+### MonoVertex Histogram Example
+
+The UI displays the configured metrics, such as the `monovtx_processing_time_bucket` histogram, with options to select dimensions and filters.
+
+![MonoVertex Histogram](../../assets/metrics/mvtx-histogram.png)
+
+### Dimension Selection
+
+Users can choose dimensions for the metric, such as `MonoVertex` or `Pod`, to view data at different levels of granularity. After selecting `Pod` dimension, we see that `Filters` box appears which allow us to add filters for a dimension. Here, `pod` dimension has pod as a filter.
+
+![Dimensions](../../assets/metrics/dimensions.png)
+
+### Quantile Selection
+
+For histogram metrics, users can select quantile values (e.g., P99, P95) from the available options, as explained in the [Key Definitions](#key-definitions) section.
+
+![Quantiles](../../assets/metrics/quantiles.png)
+
+### Query Window/Duration
+
+The query window specifies the time range over which the `rate()` function calculates the per-second average rate of increase for each histogram bucket.
+
+![Query Window](../../assets/metrics/query-window.png)
+
+### Time Range Selection
+
+Users can select a predefined time range or set a custom time range for their PromQL queries.
+
+![Time Range](../../assets/metrics/time-range.png)
+
+## Adding Custom Metrics
+
+To add metrics not provided by Numaflow, follow the configuration patterns described above. For example, you can add a custom metric similar to the `pod_cpu_memory_utilization` pattern [here](#current-configuration).
diff --git a/docs/user-guide/UI/overview.md b/docs/user-guide/UI/overview.md
new file mode 100644
index 00000000..d31c8aea
--- /dev/null
+++ b/docs/user-guide/UI/overview.md
@@ -0,0 +1,70 @@
+# Numaflow UI Overview
+
+Numaflow provides a built-in user interface (UI) for monitoring and managing your data pipelines.
+
+### Accessing the Numaflow UI
+
+To access the Numaflow UI, use the following command to port-forward the Numaflow server:
+
+```shell
+kubectl -n numaflow-system port-forward deployment/numaflow-server 8443:8443
+```
+
+Once port-forwarding is active, open your browser and navigate to [https://localhost:8443](https://localhost:8443).
+
+### UI Views
+
+We have already walked through some of the views of UI to monitor pipelines in the [Quick Start](../../quick-start.md) guide.
+
+- **Cluster View**
+- **Namespace View**
+- **Simple Pipeline View**
+
+### Vertex View
+
+Vertex View provides detailed insights into each pipeline vertex. The following features are available:
+
+#### **[Pods View](./pods-view.md)**
+
+Inspect the status and details of pods running in your pipelines.
+
+#### **Spec**
+
+View the specification of the vertex.
+
+![spec view](../../assets/ui-overview/spec-view.png)
+
+#### **Processing Rates**
+
+See the last 1m, 5m, and 15m processing rates for each partition of the vertex.
+
+If a Prometheus server is configured, you can click on the number to see more details in the Metrics tab.
+
+![Processing Rates](../../assets/ui-overview/processing-rates.png)
+
+#### **Kubernetes Events**
+
+View Kubernetes events related to the vertex.
+
+![k8s events](../../assets/ui-overview/k8s-events.png)
+
+#### **[Errors](./errors.md)**
+
+Review errors detected in your pipelines for quick debugging.
+
+#### **Buffers**
+
+See buffer details for every partition, including buffer length, usage, and number of pending messages.
+Click on the pending number to view complete pending metrics in the [Metrics tab](./metrics-tab.md).
+
+![Buffer](../../assets/ui-overview/buffer-info.png)
+
+#### **[Logs](./logs.md)**
+
+View logs for different containers of pods to help diagnose issues.
+
+#### **[Metrics](./metrics-tab.md)**
+
+Monitor pipeline metrics for performance.
+
+---
diff --git a/docs/user-guide/UI/pods-view.md b/docs/user-guide/UI/pods-view.md
new file mode 100644
index 00000000..6d9c6751
--- /dev/null
+++ b/docs/user-guide/UI/pods-view.md
@@ -0,0 +1,84 @@
+# Pods View
+
+The **Pods View** in the Numaflow UI provides a comprehensive overview of the pods associated with a vertex. This view is designed to help users monitor the status, resource usage, and other key details of the pods in their application. It also allows users to perform actions such as filtering and inspecting individual pods.
+
+---
+
+## Features of the Pods View
+
+### 1. Select a Pod by Name
+
+- Displays a list of all pods associated with the selected vertex.
+- Users can select a pod from the dropdown by its name.
+
+---
+
+### 2. Select a Pod by Resource
+
+- **CPU** and **Memory** usage of the containers within pods are displayed in a hexagon heat map.
+- Users can select a pod by clicking on any of these hexagons.
+    - This is particularly useful when a pod's heat map is red, indicating high resource usage, and you want to inspect that specific pod.
+
+---
+
+### 3. Select a Container
+
+- Allows users to select a container within a pod to view its [info](#container-info) and [logs](#container-logs).
+
+---
+
+### 4. Container Info
+
+Provides detailed information about the selected container, including:
+
+- **Name**: The name of the container.
+- **Status**: The current state of the container (e.g., `Waiting`, `Running`, or `Terminated`).
+- **Last Started At**: The relative age of the container since it was last (re-)started.
+- **CPU**: The CPU usage compared to the requested amount.
+- **Memory**: The memory usage compared to the requested amount.
+- **Restart Count**: The number of times the container has been restarted.
+- **Last Termination Reason**: The reason for the container's last termination (if applicable). This helps debug container crashes and restarts.
+- **Last Termination Message**: A message providing details about the last termination of the container (if applicable).
+- **Waiting Reason**: The reason why the container is in a waiting state (if applicable).
+- **Waiting Message**: A message providing details about why the container is in a waiting state (if applicable).
+
+---
+
+### 5. Container Logs
+
+- Quickly access the logs of a specific container within a pod by selecting it.
+- Refer to the [Logs View](./logs.md) section for a detailed explanation of log features.
+
+---
+
+### 6. Pod Info
+
+Provides detailed information about the selected pod, including:
+
+- **Name**: The name of the pod.
+- **Status**: The current phase of the pod. Possible values include:
+    - `Pending`
+    - `Running`
+    - `Succeeded`
+    - `Failed`
+    - `Unknown`
+
+    > **Note**: Learn more about pod phases [here](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#pod-phase).
+
+- **Restart Count**: The total number of restarts for all containers within the pod.
+> Since pods are recreated upon termination, this value is an aggregate of container restarts.
+- **CPU**: The aggregated CPU usage of all containers within the pod compared to the requested amount.
+- **Memory**: The aggregated memory usage of all containers within the pod compared to the requested amount.
+- **Reason**: A brief, CamelCase message indicating why the pod is in its current state.
+    - This field is populated in cases such as pod scheduling issues, eviction, termination, or node-related problems.
+- **Message**: A human-readable message providing additional details about the pod's condition.
+    - This field is populated in scenarios like pod scheduling issues, eviction, termination, or node-related problems.
+
+---
+
+### 7. Metrics Tab
+
+- The **Pods View** includes a **Metrics Tab** located next to the Logs Tab.
+- For a detailed discussion about the Metrics Tab, refer to the [Metrics](./metrics-tab.md) section.
+
+---
diff --git a/docs/user-guide/reference/conditional-forwarding.md b/docs/user-guide/reference/conditional-forwarding.md
index a08ae473..28706077 100644
--- a/docs/user-guide/reference/conditional-forwarding.md
+++ b/docs/user-guide/reference/conditional-forwarding.md
@@ -7,9 +7,31 @@ Below is a list of different logic operations that can be done on tags.
 - **or** - forwards the message if one of the tags specified is present in Message's tags.
 - **not** - forwards the message if all the tags specified are not present in Message's tags.
 
-For example, there's a UDF used to process numbers, and forward the result to different vertices based on the number is even or odd. In this case, you can set the `tag` to `even-tag` or `odd-tag` in each of the returned messages,
+For example, there's a UDF used to process numbers, and forward the result to different vertices based on the number is 
+even or odd. In this case, you can set the `tag` to `even-tag` or `odd-tag` in each of the returned messages,
 and define the edges as below:
 
+## Default Behavior
+
+* If no `conditions` are specified in the spec, the message will be forwarded to all the downstream vertices (independent
+  of the `tags` in the `Messages`).
+* In the code, if the `Messages` are not tagged but conditions are configured, we will still honour the edge conditions.
+
+## Syntax
+
+```yaml
+edges:
+  - from: ...
+    to: ...
+    conditions:
+      tags:
+        operator: ...
+        values:
+          - ...
+```
+
+## Example
+
 ```yaml
 edges:
   - from: p1
diff --git a/docs/user-guide/reference/kustomize/numaflow-transformer-config.yaml b/docs/user-guide/reference/kustomize/numaflow-transformer-config.yaml
index 37b74aba..ee28e386 100644
--- a/docs/user-guide/reference/kustomize/numaflow-transformer-config.yaml
+++ b/docs/user-guide/reference/kustomize/numaflow-transformer-config.yaml
@@ -177,7 +177,7 @@ nameReference:
         kind: Pipeline
       - path: spec/vertices/source/udsource/container/env/valueFrom/secretKeyRef/name
         kind: Pipeline
-      - path: spec/vertices/source/udousrce/container/envFrom/secretRef/name
+      - path: spec/vertices/source/udsource/container/envFrom/secretRef/name
         kind: Pipeline
       - path: spec/sideInputs/container/env/valueFrom/secretKeyRef/name
         kind: Pipeline
@@ -239,7 +239,7 @@ nameReference:
         kind: MonoVertex
       - path: spec/source/udsource/container/env/valueFrom/secretKeyRef/name
         kind: MonoVertex
-      - path: spec/source/udousrce/container/envFrom/secretRef/name
+      - path: spec/source/udsource/container/envFrom/secretRef/name
         kind: MonoVertex
       - path: spec/sink/udsink/container/env/valueFrom/secretKeyRef/name
         kind: MonoVertex
diff --git a/docs/user-guide/sinks/retry-strategy.md b/docs/user-guide/sinks/retry-strategy.md
index 486f6234..b97798de 100644
--- a/docs/user-guide/sinks/retry-strategy.md
+++ b/docs/user-guide/sinks/retry-strategy.md
@@ -2,69 +2,96 @@
 
 ### Overview
 
-The `RetryStrategy` is used to configure the behavior for a sink after encountering failures during a write operation. 
-This structure allows the user to specify how Numaflow should respond to different fail-over scenarios for Sinks, ensuring that the writing can be resilient and handle 
+The `RetryStrategy` is used to configure the behavior for a sink after encountering failures during a write operation.
+This structure allows the user to specify how Numaflow should respond to different fail-over scenarios for Sinks, ensuring that the writing can be resilient and handle
 unexpected issues efficiently.
 
 `RetryStrategy` ONLY gets applied to failed messages. To return a failed messages, use the methods provided by the SDKs.
+
 - `ResponseFailure`for [Golang](https://github.com/numaproj/numaflow-go/blob/main/pkg/sinker/types.go)
 - `responseFailure` for [Java](https://github.com/numaproj/numaflow-java/blob/main/src/main/java/io/numaproj/numaflow/sinker/Response.java#L40)
 - `as_fallback` for [Python](https://github.com/numaproj/numaflow-python/blob/main/pynumaflow/sinker/_dtypes.py)
 
-### Struct Explanation
+### Retry Strategy Configuration
+
+The `retryStrategy` section allows you to define custom retry behavior for sink operations. If no custom fields are defined, the **default** values are applied.
 
-`retryStrategy` is optional, and can be added to the Sink spec configurations where retry logic is necessary.
+#### Example Configuration
 
 ```yaml
 sink:
   retryStrategy:
-      # Optional
-      backoff:
-        duration: 1s # Optional
-        steps: 3 # Optional, number of retries (including the 1st try)
-      # Optional
-      onFailure: retry|fallback|drop 
+    # Optional
+    backoff:
+      interval: 1s # Optional, a string with timestamp suffix
+      steps: 3 # Optional, unsigned int, cannot be 0
+      factor: 1.5 # Optional, float type, >= 1
+      cap: 20s # Optional, a string with timestamp suffix
+      jitter: 0.1 # Optional, float type, >=0 and <1
+    # Optional
+    onFailure: 'fallback'
 ```
-Note: If no custom fields are defined for retryStrategy then the **default** values are used.
 
-- `BackOff` - Defines the timing for retries, including the interval and the maximum attempts.
-  - `duration`: the time interval to wait before retry attempts
-    - Default: _1ms_
-  - `steps`: the limit on the number of times to try the sink write operation including retries
-    - Default: _Infinite_
-- `OnFailure` - Specifies the action to be undertaken if number of retries are exhausted
-  - retry: continue with the retry logic again
-  - fallback: write the leftover messages to a [fallback](https://numaflow.numaproj.io/user-guide/sinks/fallback/) sink
-  - drop: any messages left to be processed are dropped
-    - Default: _retry_
+#### BackOff Parameters
+
+The `BackOff` configuration defines the timing and limits for retries. Below are the available fields:
+
+- **`interval`**: The time interval to wait before retry attempts.
+
+  - **Type**: String with a timestamp suffix.
+  - **Default**: `1ms`.
+
+- **`steps`**: The maximum number of retry attempts, including the initial attempt.
+
+  - **Type**: Unsigned integer, must be greater than 0.
+  - **Default**: Infinite.
 
+- **`factor`**: A multiplier applied to the interval after each retry attempt.
 
-### Constraints
+  - **Type**: Float, must be greater than or equal to 1.
+  - **Default**: `1.0`.
 
-1) If the `onFailure` is defined as fallback, then there should be a fallback sink specified in the spec.
+- **`cap`**: The maximum value for the interval, limiting exponential backoff growth.
 
-2) The steps defined should always be `> 0`
+  - **Type**: String with a timestamp suffix.
+  - **Default**: `indefinite` (no upper limit).
 
+- **`jitter`**: Adds randomness to the interval to avoid retry collisions.
+  - **Type**: Float, must be greater than or equal to 0 and less than 1.
+  - **Default**: `0`.
 
-## Example
+#### OnFailure Actions
+
+The `onFailure` field specifies the action to take when retries are exhausted. Available options are:
+
+- **`retry`**: Restart the retry logic.
+- **`fallback`**: Route the remaining messages to a [fallback sink](https://numaflow.numaproj.io/user-guide/sinks/fallback/).
+- **`drop`**: Discard any unprocessed messages.
+
+- **Default**: `retry`.
+
+### Sink Example with Retry Strategy
 
 ```yaml
-  sink:
-    retryStrategy:
-      backoff:
-        interval: "500ms"
-        steps: 10
-      onFailure: "fallback"
+sink:
+  retryStrategy:
+    backoff:
+      interval: 500ms
+      steps: 10
+      factor: 2.2
+      cap: 10s
+    onFailure: 'fallback'
+  udsink:
+    container:
+      image: my-sink-image
+  fallback:
     udsink:
       container:
-        image: my-sink-image
-    fallback:
-      udsink:
-        container:
-          image: my-fallback-sink
+        image: my-fallback-sink
 ```
-### Explanation
 
-- Normal Operation: Data is processed by the primary sink container specified by `UDSink`. 
-The system retries up to 10 times for a batch write operation to succeed with an interval of 500 milliseconds between each retry.
-- After Maximum Retries: If all retries fail, data is then routed to a fallback sink instead.
+#### Explanation
+
+- **Primary Sink Processing**: The main sink container (`UDSink`) processes the data. If a batch write operation fails, the system will retry up to 10 times.
+- **Retry Behavior**: The first retry happens after 500 milliseconds. Each subsequent retry interval increases by multiplying the previous interval by 2.2, up to a maximum interval of 10 seconds.
+- **Fallback Handling**: If all retries are exhausted and the operation still fails, the data is routed to a fallback sink.
diff --git a/docs/user-guide/sources/kafka.md b/docs/user-guide/sources/kafka.md
index 3f3db417..dadc8a43 100644
--- a/docs/user-guide/sources/kafka.md
+++ b/docs/user-guide/sources/kafka.md
@@ -113,7 +113,7 @@ spec:
 ### How to start the Kafka Source from a specific offset based on datetime?
 In order to start the Kafka Source from a specific offset based on datetime, we need to reset the offset before we start the pipeline.
 
-For example, we have a topic `quickstart-events` with 3 partitions and a consumer group `console-consumer-94457`. This example uses [Kafka 3.6.1](https://downloads.apache.org/kafka/3.6.1/RELEASE_NOTES.html) and localhost.
+For example, we have a topic `quickstart-events` with 3 partitions and a consumer group `console-consumer-94457`. This example uses [Kafka 3.6.1](https://archive.apache.org/dist/kafka/3.6.1/RELEASE_NOTES.html) and localhost.
 ```shell
 ➜  kafka_2.13-3.6.1 bin/kafka-topics.sh --bootstrap-server localhost:9092 --describe --topic quickstart-events            
 Topic: quickstart-events	TopicId: WqIN6j7hTQqGZUQWdF7AdA	PartitionCount: 3	ReplicationFactor: 1	Configs: 
@@ -172,4 +172,4 @@ bin/kafka-consumer-groups.sh --bootstrap-server <BOOTSTRAP_BROKER_LIST> --comman
 
 Reference:
 - [How to Use Kafka Tools With Confluent Cloud](https://docs.confluent.io/kafka/operations-tools/use-kafka-tools-ccloud.html#create-a-configuration-file)
-- [Apache Kafka Security](https://kafka.apache.org/documentation/#security)
+- [Apache Kafka Security](https://kafka.apache.org/documentation/#security)
\ No newline at end of file
diff --git a/docs/user-guide/user-defined-functions/map/examples.md b/docs/user-guide/user-defined-functions/map/examples.md
index 605f6603..02fc6c02 100644
--- a/docs/user-guide/user-defined-functions/map/examples.md
+++ b/docs/user-guide/user-defined-functions/map/examples.md
@@ -7,10 +7,10 @@ Please read [map](./map.md) to get the best out of these examples.
 ### Inter-Step Buffer Service (ISB Service)
 
 #### What is ISB Service?
+
 An Inter-Step Buffer Service is described by a [Custom Resource](https://kubernetes.io/docs/concepts/extend-kubernetes/api-extension/custom-resources/), which is used to pass data between vertices of a numaflow pipeline.
 Please refer to the doc [Inter-Step Buffer Service](../../../core-concepts/inter-step-buffer.md) for more information on ISB.
 
-
 #### How to install the ISB Service
 
 ```shell
@@ -20,7 +20,7 @@ kubectl apply -f https://raw.githubusercontent.com/numaproj/numaflow/stable/exam
 The expected output of the above command is shown below:
 
 ```shell
-$ kubectl get isbsvc 
+$ kubectl get isbsvc
 
 NAME      TYPE        PHASE     MESSAGE   AGE
 default   jetstream   Running             3d19h
@@ -110,7 +110,7 @@ kubectl logs -f even-odd-odd-sink-0-xxxxx
 
 View the UI for a pipeline at https://localhost:8443/.
 
-![Numaflow UI](../../../assets/numaflow-ui-advanced-pipeline.png)
+![Numaflow UI](../../../assets/quick-start/ui-advanced-pipeline.png)
 
 The source code of the `even-odd` [user-defined function](../user-defined-functions.md) can be found [here](https://github.com/numaproj/numaflow-go/tree/main/pkg/mapper/examples/even_odd). You also can replace the [Log](../../sinks/log.md) Sink with some other sinks like [Kafka](../../sinks/kafka.md) to forward the data to Kafka topics.
 
@@ -118,4 +118,4 @@ The pipeline can be deleted by
 
 ```shell
 kubectl delete -f https://raw.githubusercontent.com/numaproj/numaflow/main/examples/2-even-odd-pipeline.yaml
-```
\ No newline at end of file
+```
diff --git a/docs/user-guide/user-defined-functions/reduce/reduce.md b/docs/user-guide/user-defined-functions/reduce/reduce.md
index 2684c359..5d5e6b24 100644
--- a/docs/user-guide/user-defined-functions/reduce/reduce.md
+++ b/docs/user-guide/user-defined-functions/reduce/reduce.md
@@ -84,10 +84,12 @@ This applies to all the window types regardless of whether they are keyed or non
 
 ## Allowed Lateness
 
-`allowedLateness` flag on the Reduce vertex will allow late data to be
-processed by slowing the down the close-of-book operation of the Reduce vertex. Late data will be included for
-the Reduce operation as long as the late data is not later than `(CurrentWatermark - AllowedLateness)`.
-Without `allowedLateness`, late data will be rejected and dropped. Each Reduce vertex can have its own `allowedLateness`.
+`allowedLateness` flag on the Reduce vertex will allow late data to be processed by slowing the down the close-of-book 
+operation of the Reduce vertex. Late data will be included for the Reduce operation as long as the late data is not 
+later than `(CurrentWatermark - AllowedLateness)`. Without `allowedLateness`, late data will be rejected and dropped. 
+The key reason for introducing `allowedLateness` is to support further out-of-order data beyond the watermark 
+withholding, esp. when there are multiple reducers in the pipeline. Each Reduce vertex can have its own `allowedLateness`,
+but the watermark delay is additive.
 
 ```yaml
 vertices:
diff --git a/examples/1-simple-pipeline.yaml b/examples/1-simple-pipeline.yaml
index 46c4bafe..f7733c76 100644
--- a/examples/1-simple-pipeline.yaml
+++ b/examples/1-simple-pipeline.yaml
@@ -27,4 +27,4 @@ spec:
     - from: in
       to: cat
     - from: cat
-      to: out
\ No newline at end of file
+      to: out
diff --git a/hack/crdgen.sh b/hack/crdgen.sh
index a40e7193..60722d4d 100755
--- a/hack/crdgen.sh
+++ b/hack/crdgen.sh
@@ -8,7 +8,7 @@ go install -mod=vendor ./vendor/sigs.k8s.io/controller-tools/cmd/controller-gen
 
 header "Generating CRDs"
 # maxDescLen=0 avoids `kubectl apply` failing due to annotations being too long
-$(go env GOPATH)/bin/controller-gen crd:crdVersions=v1,maxDescLen=0 paths=./pkg/apis/... output:dir=config/base/crds/full
+$(go env GOPATH)/bin/controller-gen crd:crdVersions=v1,allowDangerousTypes=true,maxDescLen=0 paths=./pkg/apis/... output:dir=config/base/crds/full
 
 cp config/base/crds/full/numaflow.numaproj.io*.yaml config/base/crds/minimal/
 
diff --git a/mkdocs.yml b/mkdocs.yml
index e4944860..f8b6adff 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -55,6 +55,7 @@ nav:
           - user-guide/sources/kafka.md
           - user-guide/sources/pulsar.md
           - user-guide/sources/nats.md
+          - SQS Source: user-guide/sources/sqs.md
           - user-guide/sources/user-defined-sources.md
           - Data Transformer:
               - Overview: "user-guide/sources/transformer/overview.md"
@@ -122,6 +123,13 @@ nav:
       - Use Cases:
           - user-guide/use-cases/overview.md
           - user-guide/use-cases/monitoring-and-observability.md
+      - UI:
+          - Overview: "user-guide/UI/overview.md"
+          - Errors: "user-guide/UI/errors.md"
+          - Logs: "user-guide/UI/logs.md"
+          - Metrics: "user-guide/UI/metrics-tab.md"
+          - Pods View: "user-guide/UI/pods-view.md"
+      - FAQs: user-guide/FAQ.md
   - Operator Manual:
       - Releases ⧉: "operations/releases.md"
       - operations/installation.md
@@ -150,4 +158,4 @@ nav:
       - development/debugging.md
       - development/static-code-analysis.md
       - development/releasing.md
-  - Numaproj: https://numaproj.io
+  - Numaproj: https://numaproj.io
\ No newline at end of file
diff --git a/pkg/apis/numaflow/v1alpha1/const.go b/pkg/apis/numaflow/v1alpha1/const.go
index d3261d90..cd07d8c6 100644
--- a/pkg/apis/numaflow/v1alpha1/const.go
+++ b/pkg/apis/numaflow/v1alpha1/const.go
@@ -259,11 +259,15 @@ const (
 
 	// Retry Strategy
 
-	// DefaultRetryInterval specifies the default time interval between retry attempts.
+	// DefaultRetryInterval specifies the default initial retry duration in case of exponential backoff.
+	// In case of fixed interval retry strategy, it is the default retry interval.
 	// This value can be adjusted depending on the specific requirements
 	// for responsiveness and system load considerations.
 	DefaultRetryInterval = 1 * time.Millisecond
 
+	// DefaultMaxRetryInterval specifies the default max retry duration.
+	DefaultMaxRetryInterval = math.MaxUint32 * time.Millisecond
+
 	// DefaultRetrySteps is defined to dictate how many times the platform should attempt to retry
 	// a write operation to a sink following a failure. The value is set to math.MaxInt32 - 1,
 	// effectively indicating an almost indefinite number of retries. This large default is chosen
@@ -272,6 +276,11 @@ const (
 	// due to intermittent failures is unacceptable.
 	DefaultRetrySteps = math.MaxInt32 - 1
 
+	// DefaultFactor is the default multiplier for exponential backoff cases.
+	// It is set to 1.0 to honor the default behaviour of fixed interval retries unless explicitly
+	// configured otherwise.
+	DefaultFactor = 1.0
+
 	// DefaultOnFailureRetryStrategy specifies the strategy to be used when the write to a sink fails and
 	// the retries count specified are exhausted.
 	// Setting this to 'OnFailureRetry' means the system is configured by default
diff --git a/pkg/apis/numaflow/v1alpha1/generated.pb.go b/pkg/apis/numaflow/v1alpha1/generated.pb.go
index dbc0d6ee..626ac170 100644
--- a/pkg/apis/numaflow/v1alpha1/generated.pb.go
+++ b/pkg/apis/numaflow/v1alpha1/generated.pb.go
@@ -20,6 +20,7 @@ limitations under the License.
 package v1alpha1
 
 import (
+	encoding_binary "encoding/binary"
 	fmt "fmt"
 
 	io "io"
@@ -2542,10 +2543,38 @@ func (m *Source) XXX_DiscardUnknown() {
 
 var xxx_messageInfo_Source proto.InternalMessageInfo
 
+func (m *SqsSink) Reset()      { *m = SqsSink{} }
+func (*SqsSink) ProtoMessage() {}
+func (*SqsSink) Descriptor() ([]byte, []int) {
+	return fileDescriptor_9d0d1b17d3865563, []int{89}
+}
+func (m *SqsSink) XXX_Unmarshal(b []byte) error {
+	return m.Unmarshal(b)
+}
+func (m *SqsSink) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
+	b = b[:cap(b)]
+	n, err := m.MarshalToSizedBuffer(b)
+	if err != nil {
+		return nil, err
+	}
+	return b[:n], nil
+}
+func (m *SqsSink) XXX_Merge(src proto.Message) {
+	xxx_messageInfo_SqsSink.Merge(m, src)
+}
+func (m *SqsSink) XXX_Size() int {
+	return m.Size()
+}
+func (m *SqsSink) XXX_DiscardUnknown() {
+	xxx_messageInfo_SqsSink.DiscardUnknown(m)
+}
+
+var xxx_messageInfo_SqsSink proto.InternalMessageInfo
+
 func (m *SqsSource) Reset()      { *m = SqsSource{} }
 func (*SqsSource) ProtoMessage() {}
 func (*SqsSource) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{89}
+	return fileDescriptor_9d0d1b17d3865563, []int{90}
 }
 func (m *SqsSource) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2573,7 +2602,7 @@ var xxx_messageInfo_SqsSource proto.InternalMessageInfo
 func (m *Status) Reset()      { *m = Status{} }
 func (*Status) ProtoMessage() {}
 func (*Status) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{90}
+	return fileDescriptor_9d0d1b17d3865563, []int{91}
 }
 func (m *Status) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2601,7 +2630,7 @@ var xxx_messageInfo_Status proto.InternalMessageInfo
 func (m *TLS) Reset()      { *m = TLS{} }
 func (*TLS) ProtoMessage() {}
 func (*TLS) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{91}
+	return fileDescriptor_9d0d1b17d3865563, []int{92}
 }
 func (m *TLS) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2629,7 +2658,7 @@ var xxx_messageInfo_TLS proto.InternalMessageInfo
 func (m *TagConditions) Reset()      { *m = TagConditions{} }
 func (*TagConditions) ProtoMessage() {}
 func (*TagConditions) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{92}
+	return fileDescriptor_9d0d1b17d3865563, []int{93}
 }
 func (m *TagConditions) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2657,7 +2686,7 @@ var xxx_messageInfo_TagConditions proto.InternalMessageInfo
 func (m *Templates) Reset()      { *m = Templates{} }
 func (*Templates) ProtoMessage() {}
 func (*Templates) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{93}
+	return fileDescriptor_9d0d1b17d3865563, []int{94}
 }
 func (m *Templates) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2685,7 +2714,7 @@ var xxx_messageInfo_Templates proto.InternalMessageInfo
 func (m *Transformer) Reset()      { *m = Transformer{} }
 func (*Transformer) ProtoMessage() {}
 func (*Transformer) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{94}
+	return fileDescriptor_9d0d1b17d3865563, []int{95}
 }
 func (m *Transformer) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2713,7 +2742,7 @@ var xxx_messageInfo_Transformer proto.InternalMessageInfo
 func (m *UDF) Reset()      { *m = UDF{} }
 func (*UDF) ProtoMessage() {}
 func (*UDF) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{95}
+	return fileDescriptor_9d0d1b17d3865563, []int{96}
 }
 func (m *UDF) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2741,7 +2770,7 @@ var xxx_messageInfo_UDF proto.InternalMessageInfo
 func (m *UDSink) Reset()      { *m = UDSink{} }
 func (*UDSink) ProtoMessage() {}
 func (*UDSink) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{96}
+	return fileDescriptor_9d0d1b17d3865563, []int{97}
 }
 func (m *UDSink) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2769,7 +2798,7 @@ var xxx_messageInfo_UDSink proto.InternalMessageInfo
 func (m *UDSource) Reset()      { *m = UDSource{} }
 func (*UDSource) ProtoMessage() {}
 func (*UDSource) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{97}
+	return fileDescriptor_9d0d1b17d3865563, []int{98}
 }
 func (m *UDSource) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2797,7 +2826,7 @@ var xxx_messageInfo_UDSource proto.InternalMessageInfo
 func (m *UDTransformer) Reset()      { *m = UDTransformer{} }
 func (*UDTransformer) ProtoMessage() {}
 func (*UDTransformer) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{98}
+	return fileDescriptor_9d0d1b17d3865563, []int{99}
 }
 func (m *UDTransformer) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2825,7 +2854,7 @@ var xxx_messageInfo_UDTransformer proto.InternalMessageInfo
 func (m *UpdateStrategy) Reset()      { *m = UpdateStrategy{} }
 func (*UpdateStrategy) ProtoMessage() {}
 func (*UpdateStrategy) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{99}
+	return fileDescriptor_9d0d1b17d3865563, []int{100}
 }
 func (m *UpdateStrategy) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2853,7 +2882,7 @@ var xxx_messageInfo_UpdateStrategy proto.InternalMessageInfo
 func (m *Vertex) Reset()      { *m = Vertex{} }
 func (*Vertex) ProtoMessage() {}
 func (*Vertex) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{100}
+	return fileDescriptor_9d0d1b17d3865563, []int{101}
 }
 func (m *Vertex) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2881,7 +2910,7 @@ var xxx_messageInfo_Vertex proto.InternalMessageInfo
 func (m *VertexInstance) Reset()      { *m = VertexInstance{} }
 func (*VertexInstance) ProtoMessage() {}
 func (*VertexInstance) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{101}
+	return fileDescriptor_9d0d1b17d3865563, []int{102}
 }
 func (m *VertexInstance) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2909,7 +2938,7 @@ var xxx_messageInfo_VertexInstance proto.InternalMessageInfo
 func (m *VertexLifecycle) Reset()      { *m = VertexLifecycle{} }
 func (*VertexLifecycle) ProtoMessage() {}
 func (*VertexLifecycle) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{102}
+	return fileDescriptor_9d0d1b17d3865563, []int{103}
 }
 func (m *VertexLifecycle) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2937,7 +2966,7 @@ var xxx_messageInfo_VertexLifecycle proto.InternalMessageInfo
 func (m *VertexLimits) Reset()      { *m = VertexLimits{} }
 func (*VertexLimits) ProtoMessage() {}
 func (*VertexLimits) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{103}
+	return fileDescriptor_9d0d1b17d3865563, []int{104}
 }
 func (m *VertexLimits) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2965,7 +2994,7 @@ var xxx_messageInfo_VertexLimits proto.InternalMessageInfo
 func (m *VertexList) Reset()      { *m = VertexList{} }
 func (*VertexList) ProtoMessage() {}
 func (*VertexList) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{104}
+	return fileDescriptor_9d0d1b17d3865563, []int{105}
 }
 func (m *VertexList) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -2993,7 +3022,7 @@ var xxx_messageInfo_VertexList proto.InternalMessageInfo
 func (m *VertexSpec) Reset()      { *m = VertexSpec{} }
 func (*VertexSpec) ProtoMessage() {}
 func (*VertexSpec) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{105}
+	return fileDescriptor_9d0d1b17d3865563, []int{106}
 }
 func (m *VertexSpec) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3021,7 +3050,7 @@ var xxx_messageInfo_VertexSpec proto.InternalMessageInfo
 func (m *VertexStatus) Reset()      { *m = VertexStatus{} }
 func (*VertexStatus) ProtoMessage() {}
 func (*VertexStatus) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{106}
+	return fileDescriptor_9d0d1b17d3865563, []int{107}
 }
 func (m *VertexStatus) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3049,7 +3078,7 @@ var xxx_messageInfo_VertexStatus proto.InternalMessageInfo
 func (m *VertexTemplate) Reset()      { *m = VertexTemplate{} }
 func (*VertexTemplate) ProtoMessage() {}
 func (*VertexTemplate) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{107}
+	return fileDescriptor_9d0d1b17d3865563, []int{108}
 }
 func (m *VertexTemplate) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3077,7 +3106,7 @@ var xxx_messageInfo_VertexTemplate proto.InternalMessageInfo
 func (m *Watermark) Reset()      { *m = Watermark{} }
 func (*Watermark) ProtoMessage() {}
 func (*Watermark) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{108}
+	return fileDescriptor_9d0d1b17d3865563, []int{109}
 }
 func (m *Watermark) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3105,7 +3134,7 @@ var xxx_messageInfo_Watermark proto.InternalMessageInfo
 func (m *Window) Reset()      { *m = Window{} }
 func (*Window) ProtoMessage() {}
 func (*Window) Descriptor() ([]byte, []int) {
-	return fileDescriptor_9d0d1b17d3865563, []int{109}
+	return fileDescriptor_9d0d1b17d3865563, []int{110}
 }
 func (m *Window) XXX_Unmarshal(b []byte) error {
 	return m.Unmarshal(b)
@@ -3228,6 +3257,7 @@ func init() {
 	proto.RegisterType((*Sink)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.Sink")
 	proto.RegisterType((*SlidingWindow)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SlidingWindow")
 	proto.RegisterType((*Source)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.Source")
+	proto.RegisterType((*SqsSink)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SqsSink")
 	proto.RegisterType((*SqsSource)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.SqsSource")
 	proto.RegisterType((*Status)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.Status")
 	proto.RegisterType((*TLS)(nil), "github.com.numaproj.numaflow.pkg.apis.numaflow.v1alpha1.TLS")
@@ -3257,563 +3287,568 @@ func init() {
 }
 
 var fileDescriptor_9d0d1b17d3865563 = []byte{
-	// 8882 bytes of a gzipped FileDescriptorProto
+	// 8968 bytes of a gzipped FileDescriptorProto
 	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x7d, 0x5b, 0x6c, 0x24, 0x59,
-	0x96, 0xd0, 0xe4, 0xcb, 0x99, 0x79, 0xd2, 0x8f, 0xaa, 0x5b, 0x8f, 0x76, 0xd5, 0x74, 0x97, 0x6b,
+	0x96, 0x50, 0xe7, 0xcb, 0x99, 0x79, 0xd2, 0x8f, 0xaa, 0x5b, 0x8f, 0x76, 0xd5, 0x74, 0x97, 0x6b,
 	0xa2, 0xb7, 0x7b, 0x6a, 0xd9, 0x59, 0x9b, 0xf6, 0x4e, 0xf7, 0xf4, 0xcc, 0xec, 0x4c, 0xb7, 0xd3,
-	0x2e, 0x57, 0xb9, 0xca, 0xae, 0x72, 0x9f, 0xb4, 0xab, 0x7a, 0xa6, 0x99, 0x69, 0xc2, 0x11, 0xd7,
-	0xe9, 0x68, 0x47, 0x46, 0x64, 0x47, 0x44, 0xba, 0xca, 0xbd, 0x8c, 0x66, 0x77, 0x06, 0xe8, 0x41,
-	0x80, 0x40, 0xfb, 0xc3, 0x4a, 0x68, 0x41, 0x48, 0x48, 0xfb, 0xb1, 0x5a, 0x3e, 0x56, 0x0c, 0x42,
-	0x7c, 0x00, 0xcb, 0xc7, 0x32, 0xc0, 0x02, 0x23, 0xb4, 0x12, 0x83, 0x00, 0x8b, 0x31, 0xe2, 0x03,
-	0x24, 0xd0, 0x2e, 0x2b, 0x60, 0x29, 0x10, 0x8b, 0xee, 0x2b, 0x5e, 0x19, 0xe9, 0xb2, 0x33, 0xd2,
-	0xee, 0xea, 0xdd, 0xfe, 0x8b, 0xb8, 0xe7, 0xdc, 0x73, 0xee, 0xfb, 0x9e, 0x7b, 0xce, 0xb9, 0xe7,
-	0xc2, 0xad, 0xb6, 0x15, 0xec, 0xf4, 0xb6, 0x66, 0x0d, 0xb7, 0x33, 0xe7, 0xf4, 0x3a, 0x7a, 0xd7,
-	0x73, 0xdf, 0xe3, 0x1f, 0xdb, 0xb6, 0xfb, 0x68, 0xae, 0xbb, 0xdb, 0x9e, 0xd3, 0xbb, 0x96, 0x1f,
-	0xa5, 0xec, 0xbd, 0xa2, 0xdb, 0xdd, 0x1d, 0xfd, 0x95, 0xb9, 0x36, 0x75, 0xa8, 0xa7, 0x07, 0xd4,
-	0x9c, 0xed, 0x7a, 0x6e, 0xe0, 0x92, 0x2f, 0x44, 0x84, 0x66, 0x15, 0xa1, 0x59, 0x95, 0x6d, 0xb6,
-	0xbb, 0xdb, 0x9e, 0x65, 0x84, 0xa2, 0x14, 0x45, 0xe8, 0xea, 0x4f, 0xc7, 0x4a, 0xd0, 0x76, 0xdb,
-	0xee, 0x1c, 0xa7, 0xb7, 0xd5, 0xdb, 0xe6, 0x7f, 0xfc, 0x87, 0x7f, 0x09, 0x3e, 0x57, 0xb5, 0xdd,
-	0xd7, 0xfd, 0x59, 0xcb, 0x65, 0xc5, 0x9a, 0x33, 0x5c, 0x8f, 0xce, 0xed, 0xf5, 0x95, 0xe5, 0xea,
-	0xe7, 0x23, 0x9c, 0x8e, 0x6e, 0xec, 0x58, 0x0e, 0xf5, 0xf6, 0x55, 0x5d, 0xe6, 0x3c, 0xea, 0xbb,
-	0x3d, 0xcf, 0xa0, 0x27, 0xca, 0xe5, 0xcf, 0x75, 0x68, 0xa0, 0x67, 0xf1, 0x9a, 0x1b, 0x94, 0xcb,
-	0xeb, 0x39, 0x81, 0xd5, 0xe9, 0x67, 0xf3, 0xda, 0xd3, 0x32, 0xf8, 0xc6, 0x0e, 0xed, 0xe8, 0x7d,
-	0xf9, 0x7e, 0x66, 0x50, 0xbe, 0x5e, 0x60, 0xd9, 0x73, 0x96, 0x13, 0xf8, 0x81, 0x97, 0xce, 0xa4,
-	0xfd, 0x06, 0xc0, 0x85, 0x85, 0x2d, 0x3f, 0xf0, 0x74, 0x23, 0x58, 0x77, 0xcd, 0x0d, 0xda, 0xe9,
-	0xda, 0x7a, 0x40, 0xc9, 0x2e, 0xd4, 0x58, 0x85, 0x4c, 0x3d, 0xd0, 0xa7, 0x0b, 0xd7, 0x0b, 0x37,
-	0x1a, 0xf3, 0x0b, 0xb3, 0x43, 0x76, 0xe0, 0xec, 0x9a, 0x24, 0xd4, 0x1c, 0x3f, 0x3c, 0x98, 0xa9,
-	0xa9, 0x3f, 0x0c, 0x19, 0x90, 0x5f, 0x2a, 0xc0, 0xb8, 0xe3, 0x9a, 0xb4, 0x45, 0x6d, 0x6a, 0x04,
-	0xae, 0x37, 0x5d, 0xbc, 0x5e, 0xba, 0xd1, 0x98, 0xff, 0xe6, 0xd0, 0x1c, 0x33, 0x6a, 0x34, 0x7b,
-	0x2f, 0xc6, 0xe0, 0xa6, 0x13, 0x78, 0xfb, 0xcd, 0x8b, 0x3f, 0x38, 0x98, 0xf9, 0xd4, 0xe1, 0xc1,
-	0xcc, 0x78, 0x1c, 0x84, 0x89, 0x92, 0x90, 0x4d, 0x68, 0x04, 0xae, 0xcd, 0x9a, 0xcc, 0x72, 0x1d,
-	0x7f, 0xba, 0xc4, 0x0b, 0x76, 0x6d, 0x56, 0x34, 0x35, 0x63, 0x3f, 0xcb, 0xc6, 0xd8, 0xec, 0xde,
-	0x2b, 0xb3, 0x1b, 0x21, 0x5a, 0xf3, 0x82, 0x24, 0xdc, 0x88, 0xd2, 0x7c, 0x8c, 0xd3, 0x21, 0x14,
-	0xa6, 0x7c, 0x6a, 0xf4, 0x3c, 0x2b, 0xd8, 0x5f, 0x74, 0x9d, 0x80, 0x3e, 0x0e, 0xa6, 0xcb, 0xbc,
-	0x95, 0x5f, 0xce, 0x22, 0xbd, 0xee, 0x9a, 0xad, 0x24, 0x76, 0xf3, 0xc2, 0xe1, 0xc1, 0xcc, 0x54,
-	0x2a, 0x11, 0xd3, 0x34, 0x89, 0x03, 0xe7, 0xac, 0x8e, 0xde, 0xa6, 0xeb, 0x3d, 0xdb, 0x6e, 0x51,
-	0xc3, 0xa3, 0x81, 0x3f, 0x5d, 0xe1, 0x55, 0xb8, 0x91, 0xc5, 0x67, 0xd5, 0x35, 0x74, 0xfb, 0xfe,
-	0xd6, 0x7b, 0xd4, 0x08, 0x90, 0x6e, 0x53, 0x8f, 0x3a, 0x06, 0x6d, 0x4e, 0xcb, 0xca, 0x9c, 0x5b,
-	0x49, 0x51, 0xc2, 0x3e, 0xda, 0xe4, 0x16, 0x9c, 0xef, 0x7a, 0x96, 0xcb, 0x8b, 0x60, 0xeb, 0xbe,
-	0x7f, 0x4f, 0xef, 0xd0, 0xe9, 0xb1, 0xeb, 0x85, 0x1b, 0xf5, 0xe6, 0x15, 0x49, 0xe6, 0xfc, 0x7a,
-	0x1a, 0x01, 0xfb, 0xf3, 0x90, 0x1b, 0x50, 0x53, 0x89, 0xd3, 0xd5, 0xeb, 0x85, 0x1b, 0x15, 0x31,
-	0x76, 0x54, 0x5e, 0x0c, 0xa1, 0x64, 0x19, 0x6a, 0xfa, 0xf6, 0xb6, 0xe5, 0x30, 0xcc, 0x1a, 0x6f,
-	0xc2, 0xe7, 0xb3, 0xaa, 0xb6, 0x20, 0x71, 0x04, 0x1d, 0xf5, 0x87, 0x61, 0x5e, 0x72, 0x07, 0x88,
-	0x4f, 0xbd, 0x3d, 0xcb, 0xa0, 0x0b, 0x86, 0xe1, 0xf6, 0x9c, 0x80, 0x97, 0xbd, 0xce, 0xcb, 0x7e,
-	0x55, 0x96, 0x9d, 0xb4, 0xfa, 0x30, 0x30, 0x23, 0x17, 0x79, 0x13, 0xce, 0xc9, 0xb9, 0x1a, 0xb5,
-	0x02, 0x70, 0x4a, 0x17, 0x59, 0x43, 0x62, 0x0a, 0x86, 0x7d, 0xd8, 0xc4, 0x84, 0xe7, 0xf5, 0x5e,
-	0xe0, 0x76, 0x18, 0xc9, 0x24, 0xd3, 0x0d, 0x77, 0x97, 0x3a, 0xd3, 0x8d, 0xeb, 0x85, 0x1b, 0xb5,
-	0xe6, 0xf5, 0xc3, 0x83, 0x99, 0xe7, 0x17, 0x8e, 0xc0, 0xc3, 0x23, 0xa9, 0x90, 0xfb, 0x50, 0x37,
-	0x1d, 0x7f, 0xdd, 0xb5, 0x2d, 0x63, 0x7f, 0x7a, 0x9c, 0x17, 0xf0, 0x15, 0x59, 0xd5, 0xfa, 0xd2,
-	0xbd, 0x96, 0x00, 0x3c, 0x39, 0x98, 0x79, 0xbe, 0x7f, 0x49, 0x9d, 0x0d, 0xe1, 0x18, 0xd1, 0x20,
-	0x6b, 0x9c, 0xe0, 0xa2, 0xeb, 0x6c, 0x5b, 0xed, 0xe9, 0x09, 0xde, 0x1b, 0xd7, 0x07, 0x0c, 0xe8,
-	0xa5, 0x7b, 0x2d, 0x81, 0xd7, 0x9c, 0x90, 0xec, 0xc4, 0x2f, 0x46, 0x14, 0x88, 0x09, 0x93, 0x6a,
-	0x31, 0x5e, 0xb4, 0x75, 0xab, 0xe3, 0x4f, 0x4f, 0xf2, 0xc1, 0xfb, 0x13, 0x03, 0x68, 0x62, 0x1c,
-	0xb9, 0x79, 0x59, 0x56, 0x65, 0x32, 0x91, 0xec, 0x63, 0x8a, 0xe6, 0xd5, 0x37, 0xe0, 0x7c, 0xdf,
-	0xda, 0x40, 0xce, 0x41, 0x69, 0x97, 0xee, 0xf3, 0xa5, 0xaf, 0x8e, 0xec, 0x93, 0x5c, 0x84, 0xca,
-	0x9e, 0x6e, 0xf7, 0xe8, 0x74, 0x91, 0xa7, 0x89, 0x9f, 0x2f, 0x15, 0x5f, 0x2f, 0x68, 0x1f, 0x96,
-	0x61, 0x5c, 0xad, 0x38, 0x2d, 0xcb, 0xd9, 0x25, 0x0f, 0xa1, 0x64, 0xbb, 0x6d, 0xb9, 0x6e, 0xfe,
-	0xec, 0xd0, 0xab, 0xd8, 0xaa, 0xdb, 0x6e, 0x56, 0x0f, 0x0f, 0x66, 0x4a, 0xab, 0x6e, 0x1b, 0x19,
-	0x45, 0x62, 0x40, 0x65, 0x57, 0xdf, 0xde, 0xd5, 0x79, 0x19, 0x1a, 0xf3, 0xcd, 0xa1, 0x49, 0xdf,
-	0x65, 0x54, 0x58, 0x59, 0x9b, 0xf5, 0xc3, 0x83, 0x99, 0x0a, 0xff, 0x45, 0x41, 0x9b, 0xb8, 0x50,
-	0xdf, 0xb2, 0x75, 0x63, 0x77, 0xc7, 0xb5, 0xe9, 0x74, 0x29, 0x27, 0xa3, 0xa6, 0xa2, 0x24, 0xba,
-	0x39, 0xfc, 0xc5, 0x88, 0x07, 0x31, 0x60, 0xac, 0x67, 0xfa, 0x96, 0xb3, 0x2b, 0xd7, 0xc0, 0x37,
-	0x86, 0xe6, 0xb6, 0xb9, 0xc4, 0xeb, 0x04, 0x87, 0x07, 0x33, 0x63, 0xe2, 0x1b, 0x25, 0x69, 0xd6,
-	0x74, 0x6c, 0xa6, 0xd2, 0xe9, 0x4a, 0xce, 0x1a, 0xb1, 0x89, 0x44, 0xa3, 0xa6, 0xe3, 0xbf, 0x28,
-	0x68, 0x6b, 0xbf, 0x3f, 0x0e, 0x93, 0x6a, 0x24, 0x3c, 0xa0, 0x5e, 0x40, 0x1f, 0x93, 0xeb, 0x50,
-	0x76, 0xd8, 0xfc, 0xe7, 0x23, 0xa9, 0x39, 0x2e, 0xc7, 0x64, 0x99, 0xcf, 0x7b, 0x0e, 0x61, 0xd5,
-	0x17, 0xe3, 0x51, 0xf6, 0xea, 0xf0, 0xd5, 0x6f, 0x71, 0x32, 0xa2, 0xfa, 0xe2, 0x1b, 0x25, 0x69,
-	0xf2, 0x0e, 0x94, 0x79, 0x0b, 0x8b, 0xfe, 0xfc, 0xca, 0xf0, 0x2c, 0x58, 0xc5, 0x6b, 0xac, 0x06,
-	0xbc, 0x75, 0x39, 0x51, 0x36, 0xde, 0x7b, 0xe6, 0xb6, 0xec, 0xbd, 0x9f, 0xcd, 0xd1, 0x7b, 0xcb,
-	0x62, 0xbc, 0x6f, 0x2e, 0x2d, 0x23, 0xa3, 0x48, 0xfe, 0x52, 0x01, 0xce, 0x1b, 0xae, 0x13, 0xe8,
-	0x4c, 0x98, 0x51, 0x3b, 0xb9, 0xec, 0xc1, 0x3b, 0x43, 0xf3, 0x59, 0x4c, 0x53, 0x6c, 0x5e, 0x62,
-	0x1b, 0x53, 0x5f, 0x32, 0xf6, 0xf3, 0x26, 0x7f, 0xb5, 0x00, 0x97, 0xd8, 0x86, 0xd1, 0x87, 0xcc,
-	0xb7, 0xb9, 0xd1, 0x96, 0xea, 0xca, 0xe1, 0xc1, 0xcc, 0xa5, 0x95, 0x2c, 0x66, 0x98, 0x5d, 0x06,
-	0x56, 0xba, 0x0b, 0x7a, 0xbf, 0xec, 0xc3, 0xb7, 0xd0, 0xc6, 0xfc, 0xea, 0x28, 0xe5, 0xa9, 0xe6,
-	0xa7, 0xe5, 0x50, 0xce, 0x12, 0x1f, 0x31, 0xab, 0x14, 0xe4, 0x26, 0x54, 0xf7, 0x5c, 0xbb, 0xd7,
-	0xa1, 0xfe, 0x74, 0x8d, 0xaf, 0xe3, 0x57, 0xb3, 0xd6, 0xf1, 0x07, 0x1c, 0xa5, 0x39, 0x25, 0xc9,
-	0x57, 0xc5, 0xbf, 0x8f, 0x2a, 0x2f, 0xb1, 0x60, 0xcc, 0xb6, 0x3a, 0x56, 0xe0, 0xf3, 0xdd, 0xb9,
-	0x31, 0x7f, 0x73, 0xe8, 0x6a, 0x89, 0x29, 0xba, 0xca, 0x89, 0x89, 0x59, 0x23, 0xbe, 0x51, 0x32,
-	0xe0, 0x8b, 0x86, 0xa1, 0xdb, 0x62, 0xf7, 0x6e, 0xcc, 0x7f, 0x75, 0xf8, 0x69, 0xc3, 0xa8, 0x34,
-	0x27, 0x64, 0x9d, 0x2a, 0xfc, 0x17, 0x05, 0x6d, 0xf2, 0x0d, 0x98, 0x4c, 0xf4, 0xa6, 0x3f, 0xdd,
-	0xe0, 0xad, 0xf3, 0x42, 0x56, 0xeb, 0x84, 0x58, 0xd1, 0xf6, 0x96, 0x18, 0x21, 0x3e, 0xa6, 0x88,
-	0x91, 0xbb, 0x50, 0xf3, 0x2d, 0x93, 0x1a, 0xba, 0xe7, 0x4f, 0x8f, 0x1f, 0x87, 0xf0, 0x39, 0x49,
-	0xb8, 0xd6, 0x92, 0xd9, 0x30, 0x24, 0x40, 0x66, 0x01, 0xba, 0xba, 0x17, 0x58, 0x42, 0x1a, 0x9e,
-	0xe0, 0x92, 0xd9, 0xe4, 0xe1, 0xc1, 0x0c, 0xac, 0x87, 0xa9, 0x18, 0xc3, 0x60, 0xf8, 0x2c, 0xef,
-	0x8a, 0xd3, 0xed, 0x05, 0x62, 0xf7, 0xae, 0x0b, 0xfc, 0x56, 0x98, 0x8a, 0x31, 0x0c, 0xf2, 0x6b,
-	0x05, 0xf8, 0x74, 0xf4, 0xdb, 0x3f, 0xc9, 0xa6, 0x46, 0x3e, 0xc9, 0x66, 0x0e, 0x0f, 0x66, 0x3e,
-	0xdd, 0x1a, 0xcc, 0x12, 0x8f, 0x2a, 0x0f, 0xf9, 0xb0, 0x00, 0x93, 0xbd, 0xae, 0xa9, 0x07, 0xb4,
-	0x15, 0xb0, 0x63, 0x55, 0x7b, 0x7f, 0xfa, 0x1c, 0x2f, 0xe2, 0xad, 0xe1, 0x57, 0xc1, 0x04, 0xb9,
-	0xa8, 0x9b, 0x93, 0xe9, 0x98, 0x62, 0xab, 0xbd, 0x07, 0xe7, 0x17, 0x0c, 0xa3, 0xd7, 0xe9, 0xd9,
-	0x7a, 0xe0, 0x7a, 0x0f, 0x2d, 0xc7, 0x74, 0x1f, 0x91, 0x4d, 0xa8, 0x32, 0xb9, 0xd2, 0xed, 0x05,
-	0x52, 0x18, 0x99, 0x8d, 0x75, 0x7d, 0x78, 0x48, 0x8c, 0x4a, 0xc3, 0x4e, 0x64, 0x6c, 0x30, 0x2c,
-	0xf5, 0xe4, 0x49, 0xa6, 0xc1, 0x66, 0xe0, 0x86, 0x20, 0x81, 0x8a, 0x96, 0xf6, 0x10, 0x26, 0x16,
-	0x7a, 0xc1, 0x8e, 0xeb, 0x59, 0x1f, 0x70, 0x34, 0xb2, 0x0c, 0x95, 0x80, 0xcb, 0xa5, 0x82, 0xcb,
-	0x4b, 0x59, 0x03, 0x4c, 0x9c, 0x11, 0xee, 0xd2, 0x7d, 0x25, 0x68, 0x89, 0xfd, 0x53, 0xc8, 0xa9,
-	0x22, 0xbb, 0xf6, 0xa7, 0x0b, 0x50, 0x6d, 0xea, 0xc6, 0xae, 0xbb, 0xbd, 0x4d, 0xde, 0x86, 0x9a,
-	0xe5, 0x04, 0xd4, 0xdb, 0xd3, 0xed, 0x21, 0x0b, 0xcf, 0x45, 0xfd, 0x15, 0x49, 0x03, 0x43, 0x6a,
-	0x64, 0x06, 0x2a, 0x7e, 0x40, 0xbb, 0x3e, 0xdf, 0x6f, 0x27, 0xe4, 0x36, 0xce, 0x12, 0x50, 0xa4,
-	0x6b, 0x7f, 0xa3, 0x00, 0xf5, 0xa6, 0xee, 0x5b, 0x06, 0xab, 0x25, 0x59, 0x84, 0x72, 0xcf, 0xa7,
-	0xde, 0xc9, 0xea, 0xc6, 0xb7, 0xc8, 0x4d, 0x9f, 0x7a, 0xc8, 0x33, 0x93, 0xfb, 0x50, 0xeb, 0xea,
-	0xbe, 0xff, 0xc8, 0xf5, 0x4c, 0xb9, 0xcd, 0x1f, 0x93, 0x90, 0x38, 0xf7, 0xc8, 0xac, 0x18, 0x12,
-	0xd1, 0x1a, 0x10, 0x09, 0x53, 0xda, 0xef, 0x15, 0xe0, 0x42, 0xb3, 0xb7, 0xbd, 0x4d, 0x3d, 0x29,
-	0xe6, 0x4b, 0x01, 0x9a, 0x42, 0xc5, 0xa3, 0xa6, 0xe5, 0xcb, 0xb2, 0x2f, 0x0d, 0x3d, 0x28, 0x91,
-	0x51, 0x91, 0xf2, 0x3a, 0x6f, 0x2f, 0x9e, 0x80, 0x82, 0x3a, 0xe9, 0x41, 0xfd, 0x3d, 0x1a, 0xf8,
-	0x81, 0x47, 0xf5, 0x8e, 0xac, 0xdd, 0xed, 0xa1, 0x59, 0xdd, 0xa1, 0x41, 0x8b, 0x53, 0x8a, 0x1f,
-	0x0f, 0xc2, 0x44, 0x8c, 0x38, 0x69, 0xbf, 0x51, 0x81, 0xf1, 0x45, 0xb7, 0xb3, 0x65, 0x39, 0xd4,
-	0xbc, 0x69, 0xb6, 0x29, 0x79, 0x17, 0xca, 0xd4, 0x6c, 0x53, 0x59, 0xdb, 0xe1, 0x85, 0x1c, 0x46,
-	0x2c, 0x12, 0xd5, 0xd8, 0x1f, 0x72, 0xc2, 0x64, 0x15, 0x26, 0xb7, 0x3d, 0xb7, 0x23, 0xf6, 0x8d,
-	0x8d, 0xfd, 0xae, 0x3c, 0x0c, 0x34, 0x7f, 0x42, 0x4d, 0xd2, 0xe5, 0x04, 0xf4, 0xc9, 0xc1, 0x0c,
-	0x44, 0x7f, 0x98, 0xca, 0x4b, 0xde, 0x86, 0xe9, 0x28, 0x25, 0x5c, 0x40, 0x17, 0xd9, 0xf9, 0x8c,
-	0xcb, 0x69, 0x95, 0xe6, 0xf3, 0x87, 0x07, 0x33, 0xd3, 0xcb, 0x03, 0x70, 0x70, 0x60, 0x6e, 0xb6,
-	0x2c, 0x9d, 0x8b, 0x80, 0x62, 0x53, 0x93, 0xe2, 0xd9, 0x88, 0x76, 0x4b, 0x7e, 0x90, 0x5d, 0x4e,
-	0xb1, 0xc0, 0x3e, 0xa6, 0x64, 0x19, 0xc6, 0x03, 0x37, 0xd6, 0x5e, 0x15, 0xde, 0x5e, 0x9a, 0xd2,
-	0xbc, 0x6c, 0xb8, 0x03, 0x5b, 0x2b, 0x91, 0x8f, 0x20, 0x5c, 0x56, 0xff, 0xa9, 0x96, 0x1a, 0xe3,
-	0x2d, 0x75, 0xf5, 0xf0, 0x60, 0xe6, 0xf2, 0x46, 0x26, 0x06, 0x0e, 0xc8, 0x49, 0x7e, 0xa1, 0x00,
-	0x93, 0x0a, 0x24, 0xdb, 0xa8, 0x3a, 0xca, 0x36, 0x22, 0x6c, 0x44, 0x6c, 0x24, 0x18, 0x60, 0x8a,
-	0xa1, 0xf6, 0xfd, 0x2a, 0xd4, 0xc3, 0x6d, 0x85, 0xbc, 0x08, 0x15, 0xae, 0x53, 0x91, 0xa7, 0x85,
-	0x50, 0x5e, 0xe0, 0xaa, 0x17, 0x14, 0x30, 0xf2, 0x12, 0x54, 0x0d, 0xb7, 0xd3, 0xd1, 0x1d, 0x93,
-	0xeb, 0xc9, 0xea, 0x62, 0x91, 0x5e, 0x14, 0x49, 0xa8, 0x60, 0xe4, 0x79, 0x28, 0xeb, 0x5e, 0x5b,
-	0xa8, 0xac, 0xea, 0x62, 0x3d, 0x5a, 0xf0, 0xda, 0x3e, 0xf2, 0x54, 0xf2, 0x45, 0x28, 0x51, 0x67,
-	0x6f, 0xba, 0x3c, 0x58, 0x0e, 0xbb, 0xe9, 0xec, 0x3d, 0xd0, 0xbd, 0x66, 0x43, 0x96, 0xa1, 0x74,
-	0xd3, 0xd9, 0x43, 0x96, 0x87, 0xac, 0x42, 0x95, 0x3a, 0x7b, 0xac, 0xef, 0xa5, 0x2e, 0xe9, 0x33,
-	0x03, 0xb2, 0x33, 0x14, 0x79, 0x24, 0x09, 0xa5, 0x39, 0x99, 0x8c, 0x8a, 0x04, 0xf9, 0x1a, 0x8c,
-	0x0b, 0xc1, 0x6e, 0x8d, 0xf5, 0x89, 0x3f, 0x3d, 0xc6, 0x49, 0xce, 0x0c, 0x96, 0x0c, 0x39, 0x5e,
-	0xa4, 0xbb, 0x8b, 0x25, 0xfa, 0x98, 0x20, 0x45, 0xbe, 0x06, 0x75, 0x75, 0xd4, 0x57, 0x3d, 0x9b,
-	0xa9, 0xf6, 0x52, 0xfa, 0x01, 0xa4, 0xef, 0xf7, 0x2c, 0x8f, 0x76, 0xa8, 0x13, 0xf8, 0xcd, 0xf3,
-	0x4a, 0x11, 0xa2, 0xa0, 0x3e, 0x46, 0xd4, 0xc8, 0x56, 0xbf, 0xfe, 0x4e, 0x28, 0x9f, 0x5e, 0x1c,
-	0xb0, 0xaa, 0x0f, 0xa1, 0xbc, 0xfb, 0x26, 0x4c, 0x85, 0x0a, 0x36, 0xa9, 0xa3, 0x11, 0xea, 0xa8,
-	0xcf, 0xb3, 0xec, 0x2b, 0x49, 0xd0, 0x93, 0x83, 0x99, 0x17, 0x32, 0xb4, 0x34, 0x11, 0x02, 0xa6,
-	0x89, 0x91, 0x0f, 0x60, 0xd2, 0xa3, 0xba, 0x69, 0x39, 0xd4, 0xf7, 0xd7, 0x3d, 0x77, 0x2b, 0xbf,
-	0x94, 0xcb, 0xa9, 0x88, 0x61, 0x8f, 0x09, 0xca, 0x98, 0xe2, 0x44, 0x1e, 0xc1, 0x84, 0x6d, 0xed,
-	0xd1, 0x88, 0x75, 0x63, 0x24, 0xac, 0xcf, 0x1f, 0x1e, 0xcc, 0x4c, 0xac, 0xc6, 0x09, 0x63, 0x92,
-	0x0f, 0x93, 0x54, 0xba, 0xae, 0x17, 0x28, 0x51, 0xf8, 0x33, 0x47, 0x8a, 0xc2, 0xeb, 0xae, 0x17,
-	0x44, 0x93, 0x90, 0xfd, 0xf9, 0x28, 0xb2, 0x6b, 0x7f, 0xbb, 0x02, 0xfd, 0x07, 0xc6, 0xe4, 0x88,
-	0x2b, 0x8c, 0x7a, 0xc4, 0xa5, 0x47, 0x83, 0xd8, 0x7b, 0x5e, 0x97, 0xd9, 0x46, 0x30, 0x22, 0x32,
-	0x46, 0x75, 0x69, 0xd4, 0xa3, 0xfa, 0x99, 0x59, 0x78, 0xfa, 0x87, 0xff, 0xd8, 0x47, 0x37, 0xfc,
-	0xab, 0x67, 0x33, 0xfc, 0xb5, 0xef, 0x95, 0x61, 0x72, 0x49, 0xa7, 0x1d, 0xd7, 0x79, 0xaa, 0xce,
-	0xa0, 0xf0, 0x4c, 0xe8, 0x0c, 0x6e, 0x40, 0xcd, 0xa3, 0x5d, 0xdb, 0x32, 0x74, 0x21, 0xae, 0x4b,
-	0x43, 0x00, 0xca, 0x34, 0x0c, 0xa1, 0x03, 0x74, 0x45, 0xa5, 0x67, 0x52, 0x57, 0x54, 0xfe, 0xe8,
-	0x75, 0x45, 0xda, 0x2f, 0x14, 0x81, 0x8b, 0xb6, 0xe4, 0x3a, 0x94, 0x99, 0xd8, 0x96, 0xd6, 0x50,
-	0xf2, 0xd9, 0xc2, 0x21, 0xe4, 0x2a, 0x14, 0x03, 0x57, 0x2e, 0x37, 0x20, 0xe1, 0xc5, 0x0d, 0x17,
-	0x8b, 0x81, 0x4b, 0x3e, 0x00, 0x30, 0x5c, 0xc7, 0xb4, 0x94, 0x7d, 0x2c, 0x5f, 0xc5, 0x96, 0x5d,
-	0xef, 0x91, 0xee, 0x99, 0x8b, 0x21, 0x45, 0xa1, 0x2d, 0x88, 0xfe, 0x31, 0xc6, 0x8d, 0xbc, 0x01,
-	0x63, 0xae, 0xb3, 0xdc, 0xb3, 0x6d, 0xde, 0xa0, 0xf5, 0xe6, 0x67, 0x0f, 0x0f, 0x66, 0xc6, 0xee,
-	0xf3, 0x94, 0x27, 0x07, 0x33, 0x57, 0xc4, 0x89, 0x88, 0xfd, 0x3d, 0xf4, 0xac, 0xc0, 0x72, 0xda,
-	0xe1, 0xe1, 0x59, 0x66, 0xd3, 0x7e, 0xb1, 0x00, 0x8d, 0x65, 0xeb, 0x31, 0x35, 0xe5, 0x79, 0x19,
-	0x61, 0xcc, 0xa6, 0x4e, 0x3b, 0xd8, 0x19, 0xf2, 0xc4, 0x29, 0x74, 0x48, 0x9c, 0x02, 0x4a, 0x4a,
-	0x64, 0x0e, 0xea, 0xe2, 0xbc, 0x62, 0x39, 0x6d, 0xde, 0x86, 0xb5, 0x68, 0xa5, 0x6f, 0x29, 0x00,
-	0x46, 0x38, 0xda, 0x3e, 0x9c, 0xef, 0x6b, 0x06, 0x62, 0x42, 0x39, 0xd0, 0xdb, 0x6a, 0x53, 0x59,
-	0x1e, 0xba, 0x81, 0x37, 0xf4, 0x76, 0xac, 0x71, 0xb9, 0x54, 0xb8, 0xa1, 0x33, 0xa9, 0x90, 0x51,
-	0xd7, 0xfe, 0x6f, 0x01, 0x6a, 0xcb, 0x3d, 0xc7, 0xe0, 0x87, 0xfa, 0xa7, 0x6b, 0xae, 0x95, 0x88,
-	0x59, 0xcc, 0x14, 0x31, 0x7b, 0x30, 0xb6, 0xfb, 0x28, 0x14, 0x41, 0x1b, 0xf3, 0x6b, 0xc3, 0x8f,
-	0x0a, 0x59, 0xa4, 0xd9, 0xbb, 0x9c, 0x9e, 0xb0, 0xde, 0x4e, 0xca, 0x02, 0x8d, 0xdd, 0x7d, 0xc8,
-	0x99, 0x4a, 0x66, 0x57, 0xbf, 0x08, 0x8d, 0x18, 0xda, 0x89, 0x0c, 0x39, 0x7f, 0xa7, 0x0c, 0x63,
-	0xb7, 0x5a, 0xad, 0x85, 0xf5, 0x15, 0xf2, 0x2a, 0x34, 0xa4, 0x61, 0xef, 0x5e, 0xd4, 0x06, 0xa1,
-	0x5d, 0xb7, 0x15, 0x81, 0x30, 0x8e, 0xc7, 0x04, 0x78, 0x8f, 0xea, 0x76, 0x47, 0x4e, 0x96, 0x50,
-	0x76, 0x40, 0x96, 0x88, 0x02, 0x46, 0x74, 0x98, 0xec, 0xf9, 0xd4, 0x63, 0x4d, 0x28, 0xce, 0xfb,
-	0x72, 0xda, 0x1c, 0x53, 0x23, 0xc0, 0x37, 0x98, 0xcd, 0x04, 0x01, 0x4c, 0x11, 0x24, 0xaf, 0x43,
-	0x4d, 0xef, 0x05, 0x3b, 0xfc, 0xc8, 0x25, 0xe6, 0xc6, 0xf3, 0xdc, 0xee, 0x29, 0xd3, 0x9e, 0x1c,
-	0xcc, 0x8c, 0xdf, 0xc5, 0xe6, 0xab, 0xea, 0x1f, 0x43, 0x6c, 0x56, 0x38, 0xa5, 0x63, 0x90, 0x85,
-	0xab, 0x9c, 0xb8, 0x70, 0xeb, 0x09, 0x02, 0x98, 0x22, 0x48, 0xde, 0x81, 0xf1, 0x5d, 0xba, 0x1f,
-	0xe8, 0x5b, 0x92, 0xc1, 0xd8, 0x49, 0x18, 0x9c, 0x63, 0x42, 0xff, 0xdd, 0x58, 0x76, 0x4c, 0x10,
-	0x23, 0x3e, 0x5c, 0xdc, 0xa5, 0xde, 0x16, 0xf5, 0x5c, 0xa9, 0xaf, 0x90, 0x4c, 0xaa, 0x27, 0x61,
-	0x32, 0x7d, 0x78, 0x30, 0x73, 0xf1, 0x6e, 0x06, 0x19, 0xcc, 0x24, 0xae, 0xfd, 0xef, 0x22, 0x4c,
-	0xdd, 0x12, 0x9e, 0x15, 0xae, 0x27, 0x24, 0x0f, 0x72, 0x05, 0x4a, 0x5e, 0xb7, 0xc7, 0x47, 0x4e,
-	0x49, 0x98, 0x35, 0x70, 0x7d, 0x13, 0x59, 0x1a, 0x79, 0x1b, 0x6a, 0xa6, 0x5c, 0x32, 0xa4, 0xba,
-	0x64, 0x28, 0xd5, 0x96, 0xfa, 0xc3, 0x90, 0x1a, 0x3b, 0x1b, 0x76, 0xfc, 0x76, 0xcb, 0xfa, 0x80,
-	0x4a, 0x0d, 0x02, 0x3f, 0x1b, 0xae, 0x89, 0x24, 0x54, 0x30, 0xb6, 0xab, 0xee, 0xd2, 0x7d, 0x71,
-	0x7e, 0x2e, 0x47, 0xbb, 0xea, 0x5d, 0x99, 0x86, 0x21, 0x94, 0xcc, 0xa8, 0xc9, 0xc2, 0x46, 0x41,
-	0x59, 0xe8, 0x7e, 0x1e, 0xb0, 0x04, 0x39, 0x6f, 0xd8, 0x92, 0xf9, 0x9e, 0x15, 0x04, 0xd4, 0x93,
-	0xdd, 0x38, 0xd4, 0x92, 0x79, 0x87, 0x53, 0x40, 0x49, 0x89, 0xfc, 0x14, 0xd4, 0x39, 0xf1, 0xa6,
-	0xed, 0x6e, 0xf1, 0x8e, 0xab, 0x0b, 0x2d, 0xd0, 0x03, 0x95, 0x88, 0x11, 0x5c, 0xfb, 0x83, 0x22,
-	0x5c, 0xbe, 0x45, 0x03, 0x21, 0xd5, 0x2c, 0xd1, 0xae, 0xed, 0xee, 0x33, 0x79, 0x1a, 0xe9, 0xfb,
-	0xe4, 0x4d, 0x00, 0xcb, 0xdf, 0x6a, 0xed, 0x19, 0x7c, 0x1e, 0x88, 0x39, 0x7c, 0x5d, 0x4e, 0x49,
-	0x58, 0x69, 0x35, 0x25, 0xe4, 0x49, 0xe2, 0x0f, 0x63, 0x79, 0xa2, 0x03, 0x79, 0xf1, 0x88, 0x03,
-	0x79, 0x0b, 0xa0, 0x1b, 0x49, 0xe5, 0x25, 0x8e, 0xf9, 0x33, 0x8a, 0xcd, 0x49, 0x04, 0xf2, 0x18,
-	0x99, 0x3c, 0x72, 0xb2, 0x03, 0xe7, 0x4c, 0xba, 0xad, 0xf7, 0xec, 0x20, 0x3c, 0x49, 0xc8, 0x49,
-	0x7c, 0xfc, 0xc3, 0x48, 0xe8, 0xf5, 0xb1, 0x94, 0xa2, 0x84, 0x7d, 0xb4, 0xb5, 0xbf, 0x57, 0x82,
-	0xab, 0xb7, 0x68, 0x10, 0xea, 0xe8, 0xe4, 0xea, 0xd8, 0xea, 0x52, 0x83, 0xf5, 0xc2, 0x87, 0x05,
-	0x18, 0xb3, 0xf5, 0x2d, 0x6a, 0xb3, 0xdd, 0x8b, 0xd5, 0xe6, 0xdd, 0xa1, 0x37, 0x82, 0xc1, 0x5c,
-	0x66, 0x57, 0x39, 0x87, 0xd4, 0xd6, 0x20, 0x12, 0x51, 0xb2, 0x67, 0x8b, 0xba, 0x61, 0xf7, 0xfc,
-	0x40, 0x9c, 0xec, 0xa4, 0x3c, 0x19, 0x2e, 0xea, 0x8b, 0x11, 0x08, 0xe3, 0x78, 0x64, 0x1e, 0xc0,
-	0xb0, 0x2d, 0xea, 0x04, 0x3c, 0x97, 0x98, 0x57, 0x44, 0xf5, 0xef, 0x62, 0x08, 0xc1, 0x18, 0x16,
-	0x63, 0xd5, 0x71, 0x1d, 0x2b, 0x70, 0x05, 0xab, 0x72, 0x92, 0xd5, 0x5a, 0x04, 0xc2, 0x38, 0x1e,
-	0xcf, 0x46, 0x03, 0xcf, 0x32, 0x7c, 0x9e, 0xad, 0x92, 0xca, 0x16, 0x81, 0x30, 0x8e, 0xc7, 0xf6,
-	0xbc, 0x58, 0xfd, 0x4f, 0xb4, 0xe7, 0xfd, 0x6a, 0x1d, 0xae, 0x25, 0x9a, 0x35, 0xd0, 0x03, 0xba,
-	0xdd, 0xb3, 0x5b, 0x34, 0x50, 0x1d, 0x38, 0xe4, 0x5e, 0xf8, 0xe7, 0xa3, 0x7e, 0x17, 0xfe, 0x5c,
-	0xc6, 0x68, 0xfa, 0xbd, 0xaf, 0x80, 0xc7, 0xea, 0xfb, 0x39, 0xa8, 0x3b, 0x7a, 0xe0, 0xf3, 0x89,
-	0x2b, 0xe7, 0x68, 0x28, 0x86, 0xdd, 0x53, 0x00, 0x8c, 0x70, 0xc8, 0x3a, 0x5c, 0x94, 0x4d, 0x7c,
-	0xf3, 0x31, 0x3b, 0xf3, 0x53, 0x4f, 0xe4, 0x95, 0xdb, 0xa9, 0xcc, 0x7b, 0x71, 0x2d, 0x03, 0x07,
-	0x33, 0x73, 0x92, 0x35, 0xb8, 0x60, 0x08, 0x1f, 0x17, 0x6a, 0xbb, 0xba, 0xa9, 0x08, 0x0a, 0x95,
-	0x68, 0x78, 0x34, 0x5a, 0xec, 0x47, 0xc1, 0xac, 0x7c, 0xe9, 0xd1, 0x3c, 0x36, 0xd4, 0x68, 0xae,
-	0x0e, 0x33, 0x9a, 0x6b, 0xc3, 0x8d, 0xe6, 0xfa, 0xf1, 0x46, 0x33, 0x6b, 0x79, 0xee, 0x4e, 0xe1,
-	0x31, 0xf1, 0x44, 0xec, 0xb0, 0x31, 0x17, 0xaa, 0xb0, 0xe5, 0x5b, 0x19, 0x38, 0x98, 0x99, 0x93,
-	0x6c, 0xc1, 0x55, 0x91, 0x7e, 0xd3, 0x31, 0xbc, 0xfd, 0x2e, 0xdb, 0x78, 0x62, 0x74, 0x1b, 0x09,
-	0x9d, 0xf4, 0xd5, 0xd6, 0x40, 0x4c, 0x3c, 0x82, 0x0a, 0xf9, 0x32, 0x4c, 0x88, 0x5e, 0x5a, 0xd3,
-	0xbb, 0x9c, 0xac, 0x70, 0xa8, 0xba, 0x24, 0xc9, 0x4e, 0x2c, 0xc6, 0x81, 0x98, 0xc4, 0x25, 0x0b,
-	0x30, 0xd5, 0xdd, 0x33, 0xd8, 0xe7, 0xca, 0xf6, 0x3d, 0x4a, 0x4d, 0x6a, 0x72, 0xe3, 0x6a, 0xbd,
-	0xf9, 0x9c, 0xd2, 0xee, 0xac, 0x27, 0xc1, 0x98, 0xc6, 0x27, 0xaf, 0xc3, 0xb8, 0x1f, 0xe8, 0x5e,
-	0x20, 0x15, 0xc1, 0xd3, 0x93, 0xc2, 0xe1, 0x4c, 0xe9, 0x49, 0x5b, 0x31, 0x18, 0x26, 0x30, 0x33,
-	0xf7, 0x8b, 0xa9, 0xd3, 0xdb, 0x2f, 0xf2, 0xac, 0x56, 0xff, 0xb8, 0x08, 0xd7, 0x6f, 0xd1, 0x60,
-	0xcd, 0x75, 0xa4, 0x1a, 0x3d, 0x6b, 0xdb, 0x3f, 0x96, 0x16, 0x3d, 0xb9, 0x69, 0x17, 0x47, 0xba,
-	0x69, 0x97, 0x46, 0xb4, 0x69, 0x97, 0x4f, 0x71, 0xd3, 0xfe, 0xfb, 0x45, 0x78, 0x2e, 0xd1, 0x92,
-	0xeb, 0xae, 0xa9, 0x16, 0xfc, 0x4f, 0x1a, 0xf0, 0x18, 0x0d, 0xf8, 0x44, 0xc8, 0x9d, 0xdc, 0x10,
-	0x9a, 0x92, 0x78, 0xbe, 0x9b, 0x96, 0x78, 0xde, 0xc9, 0xb3, 0xf3, 0x65, 0x70, 0x38, 0xd6, 0x8e,
-	0x77, 0x07, 0x88, 0x27, 0xcd, 0xb6, 0x91, 0x3a, 0x5b, 0x0a, 0x3d, 0xa1, 0x47, 0x2b, 0xf6, 0x61,
-	0x60, 0x46, 0x2e, 0xd2, 0x82, 0x4b, 0x3e, 0x75, 0x02, 0xcb, 0xa1, 0x76, 0x92, 0x9c, 0x90, 0x86,
-	0x5e, 0x90, 0xe4, 0x2e, 0xb5, 0xb2, 0x90, 0x30, 0x3b, 0x6f, 0x9e, 0x75, 0xe0, 0xb7, 0x80, 0x8b,
-	0x9c, 0xa2, 0x69, 0x46, 0x26, 0xb1, 0x7c, 0x98, 0x96, 0x58, 0xde, 0xcd, 0xdf, 0x6f, 0xc3, 0x49,
-	0x2b, 0xf3, 0x00, 0xbc, 0x17, 0xe2, 0xe2, 0x4a, 0xb8, 0x49, 0x63, 0x08, 0xc1, 0x18, 0x16, 0xdb,
-	0x80, 0x54, 0x3b, 0xc7, 0x25, 0x95, 0x70, 0x03, 0x6a, 0xc5, 0x81, 0x98, 0xc4, 0x1d, 0x28, 0xed,
-	0x54, 0x86, 0x96, 0x76, 0xee, 0x00, 0x49, 0x28, 0x1e, 0x05, 0xbd, 0xb1, 0xa4, 0x43, 0xf5, 0x4a,
-	0x1f, 0x06, 0x66, 0xe4, 0x1a, 0x30, 0x94, 0xab, 0xa3, 0x1d, 0xca, 0xb5, 0xe1, 0x87, 0x32, 0x79,
-	0x17, 0xae, 0x70, 0x56, 0xb2, 0x7d, 0x92, 0x84, 0x85, 0xdc, 0xf3, 0x19, 0x49, 0xf8, 0x0a, 0x0e,
-	0x42, 0xc4, 0xc1, 0x34, 0x58, 0xff, 0x18, 0x1e, 0x35, 0x19, 0x73, 0xdd, 0x1e, 0x2c, 0x13, 0x2d,
-	0x66, 0xe0, 0x60, 0x66, 0x4e, 0x36, 0xc4, 0x02, 0x36, 0x0c, 0xf5, 0x2d, 0x9b, 0x9a, 0xd2, 0xa1,
-	0x3c, 0x1c, 0x62, 0x1b, 0xab, 0x2d, 0x09, 0xc1, 0x18, 0x56, 0x96, 0x98, 0x32, 0x7e, 0x42, 0x31,
-	0xe5, 0x16, 0xd7, 0xd2, 0x6f, 0x27, 0xa4, 0x21, 0x29, 0xeb, 0x84, 0x57, 0x04, 0x16, 0xd3, 0x08,
-	0xd8, 0x9f, 0x87, 0x4b, 0x89, 0x86, 0x67, 0x75, 0x03, 0x3f, 0x49, 0x6b, 0x32, 0x25, 0x25, 0x66,
-	0xe0, 0x60, 0x66, 0x4e, 0x26, 0x9f, 0xef, 0x50, 0xdd, 0x0e, 0x76, 0x92, 0x04, 0xa7, 0x92, 0xf2,
-	0xf9, 0xed, 0x7e, 0x14, 0xcc, 0xca, 0x97, 0xb9, 0x21, 0x9d, 0x7b, 0x36, 0xc5, 0xaa, 0x7f, 0x5e,
-	0x82, 0x17, 0x6e, 0x51, 0x71, 0x47, 0xc0, 0x69, 0xaf, 0x5b, 0x5d, 0x6a, 0x5b, 0x0e, 0x8d, 0x95,
-	0x88, 0xfc, 0xd9, 0x02, 0x8c, 0x0b, 0xbd, 0x88, 0xf4, 0xee, 0xcf, 0x6b, 0x1e, 0xca, 0x70, 0x57,
-	0x8a, 0x84, 0x55, 0xa1, 0x8d, 0x91, 0x27, 0xa1, 0x04, 0xdf, 0x4f, 0x34, 0x32, 0xc7, 0x91, 0x4d,
-	0xbe, 0x53, 0x82, 0x2b, 0xac, 0x3f, 0x95, 0xe7, 0xe2, 0x27, 0x6a, 0xb1, 0x8f, 0xa0, 0x13, 0x7e,
-	0xa5, 0x02, 0x17, 0x6e, 0xd1, 0xa0, 0x4f, 0xba, 0xfe, 0x23, 0xda, 0xfc, 0x6b, 0x70, 0x21, 0xf2,
-	0xa4, 0x6d, 0x05, 0xae, 0x27, 0x64, 0xb3, 0x94, 0xf6, 0xa3, 0xd5, 0x8f, 0x82, 0x59, 0xf9, 0xc8,
-	0xd7, 0xe0, 0x39, 0x5f, 0x2c, 0x57, 0x42, 0xdf, 0x2e, 0x94, 0x43, 0xb1, 0x0b, 0x67, 0x33, 0x92,
-	0xe4, 0x73, 0xad, 0x6c, 0x34, 0x1c, 0x94, 0x9f, 0x7c, 0x1b, 0xc6, 0xbb, 0x72, 0x09, 0x64, 0x7d,
-	0x96, 0xdb, 0x29, 0x6c, 0x3d, 0x46, 0x2c, 0x5a, 0xe3, 0xe2, 0xa9, 0x98, 0x60, 0x98, 0x39, 0x52,
-	0x6b, 0xa7, 0x38, 0x52, 0xff, 0x7b, 0x11, 0xaa, 0xb7, 0x3c, 0xb7, 0xd7, 0x6d, 0xee, 0x93, 0x36,
-	0x8c, 0x3d, 0xe2, 0xc6, 0x50, 0xb9, 0xc2, 0x0f, 0x7f, 0x1b, 0x45, 0xd8, 0x54, 0x23, 0x11, 0x57,
-	0xfc, 0xa3, 0x24, 0xcf, 0x06, 0xf1, 0x2e, 0xdd, 0xa7, 0xa6, 0xb4, 0x89, 0x86, 0x83, 0xf8, 0x2e,
-	0x4b, 0x44, 0x01, 0x23, 0x1d, 0x98, 0xd2, 0x6d, 0xdb, 0x7d, 0x44, 0xcd, 0x55, 0x3d, 0xe0, 0x7e,
-	0x0c, 0xd2, 0x56, 0x76, 0x52, 0x33, 0x03, 0x77, 0x4e, 0x59, 0x48, 0x92, 0xc2, 0x34, 0x6d, 0xf2,
-	0x1e, 0x54, 0xfd, 0xc0, 0xf5, 0x94, 0xf0, 0xdc, 0x98, 0x5f, 0x1c, 0xbe, 0xd3, 0x9b, 0x6f, 0xb5,
-	0x04, 0x29, 0x61, 0x83, 0x91, 0x3f, 0xa8, 0x18, 0x68, 0xbf, 0x5c, 0x00, 0xb8, 0xbd, 0xb1, 0xb1,
-	0x2e, 0xcd, 0x45, 0x26, 0x94, 0xf5, 0x5e, 0x68, 0x78, 0x1e, 0xde, 0xc0, 0x9b, 0x70, 0xcc, 0x96,
-	0x36, 0xd9, 0x5e, 0xb0, 0x83, 0x9c, 0x3a, 0xf9, 0x49, 0xa8, 0xca, 0x03, 0x8f, 0x6c, 0xf6, 0xd0,
-	0x3f, 0x46, 0xee, 0xc4, 0xa8, 0xe0, 0xda, 0xaf, 0x17, 0x01, 0x56, 0x4c, 0x9b, 0xb6, 0xd4, 0x05,
-	0xa2, 0x7a, 0xb0, 0xe3, 0x51, 0x7f, 0xc7, 0xb5, 0xcd, 0x21, 0xad, 0xe3, 0xdc, 0x86, 0xb3, 0xa1,
-	0x88, 0x60, 0x44, 0x8f, 0x98, 0x30, 0xee, 0x07, 0xb4, 0xab, 0x7c, 0xb5, 0x87, 0x34, 0x8a, 0x9d,
-	0x13, 0x7a, 0xae, 0x88, 0x0e, 0x26, 0xa8, 0x12, 0x1d, 0x1a, 0x96, 0x63, 0x88, 0x09, 0xd2, 0xdc,
-	0x1f, 0x72, 0x20, 0x4d, 0xb1, 0x13, 0xe4, 0x4a, 0x44, 0x06, 0xe3, 0x34, 0xb5, 0xdf, 0x29, 0xc2,
-	0x65, 0xce, 0x8f, 0x15, 0x23, 0x21, 0xe2, 0x90, 0x3f, 0xd9, 0x77, 0xa3, 0xfa, 0x8f, 0x1f, 0x8f,
-	0xb5, 0xb8, 0x90, 0xbb, 0x46, 0x03, 0x3d, 0x92, 0xcf, 0xa3, 0xb4, 0xd8, 0x35, 0xea, 0x1e, 0x94,
-	0x7d, 0xb6, 0x5e, 0x89, 0xd6, 0x6b, 0x0d, 0x3d, 0x84, 0xb2, 0x2b, 0xc0, 0x57, 0xaf, 0xd0, 0x0b,
-	0x80, 0xaf, 0x5a, 0x9c, 0x1d, 0xf9, 0x16, 0x8c, 0xf9, 0x81, 0x1e, 0xf4, 0xd4, 0xd4, 0xdc, 0x1c,
-	0x35, 0x63, 0x4e, 0x3c, 0x5a, 0x47, 0xc4, 0x3f, 0x4a, 0xa6, 0xda, 0xef, 0x14, 0xe0, 0x6a, 0x76,
-	0xc6, 0x55, 0xcb, 0x0f, 0xc8, 0x9f, 0xe8, 0x6b, 0xf6, 0x63, 0xf6, 0x38, 0xcb, 0xcd, 0x1b, 0x3d,
-	0xbc, 0x0f, 0xa3, 0x52, 0x62, 0x4d, 0x1e, 0x40, 0xc5, 0x0a, 0x68, 0x47, 0xe9, 0x0b, 0xee, 0x8f,
-	0xb8, 0xea, 0xb1, 0xad, 0x9d, 0x71, 0x41, 0xc1, 0x4c, 0xfb, 0x5e, 0x71, 0x50, 0x95, 0xf9, 0xf6,
-	0x61, 0x27, 0xbd, 0xfe, 0xef, 0xe6, 0xf3, 0xfa, 0x4f, 0x16, 0xa8, 0xdf, 0xf9, 0xff, 0x4f, 0xf5,
-	0x3b, 0xff, 0xdf, 0xcf, 0xef, 0xfc, 0x9f, 0x6a, 0x86, 0x81, 0x77, 0x00, 0x7e, 0x54, 0x82, 0xe7,
-	0x8f, 0x1a, 0x36, 0x6c, 0x3f, 0x93, 0xa3, 0x33, 0xef, 0x7e, 0x76, 0xf4, 0x38, 0x24, 0xf3, 0x50,
-	0xe9, 0xee, 0xe8, 0xbe, 0x12, 0xca, 0x9e, 0x0f, 0xdd, 0x46, 0x59, 0xe2, 0x13, 0xb6, 0x68, 0x70,
-	0x61, 0x8e, 0xff, 0xa2, 0x40, 0x65, 0xcb, 0x71, 0x87, 0xfa, 0x7e, 0xa4, 0xe3, 0x09, 0x97, 0xe3,
-	0x35, 0x91, 0x8c, 0x0a, 0x4e, 0x02, 0x18, 0x13, 0x26, 0x03, 0xb9, 0x33, 0x8d, 0xf6, 0xe4, 0x15,
-	0x56, 0x4a, 0x9e, 0xb9, 0x24, 0x2f, 0x32, 0x0b, 0xe5, 0x20, 0x72, 0xdb, 0x57, 0xaa, 0x96, 0x72,
-	0x86, 0x7c, 0xca, 0xf1, 0xc8, 0x1d, 0x20, 0xee, 0x16, 0x37, 0x92, 0x98, 0xd2, 0x1f, 0xc2, 0x72,
-	0x1d, 0x2e, 0x90, 0x95, 0x22, 0x45, 0xcd, 0xfd, 0x3e, 0x0c, 0xcc, 0xc8, 0xa5, 0xfd, 0xcb, 0x1a,
-	0x5c, 0xce, 0x1e, 0x0f, 0xac, 0xdd, 0xf6, 0xa8, 0xe7, 0x33, 0xda, 0x85, 0x64, 0xbb, 0x3d, 0x10,
-	0xc9, 0xa8, 0xe0, 0x1f, 0x6b, 0x07, 0xc2, 0x5f, 0x29, 0xc0, 0x15, 0x4f, 0xda, 0xfc, 0xce, 0xc2,
-	0x89, 0xf0, 0x05, 0xa1, 0x9e, 0x1a, 0xc0, 0x10, 0x07, 0x97, 0x85, 0xfc, 0xcd, 0x02, 0x4c, 0x77,
-	0x52, 0x7a, 0xab, 0x53, 0xbc, 0xaf, 0xcb, 0xef, 0xc5, 0xac, 0x0d, 0xe0, 0x87, 0x03, 0x4b, 0x42,
-	0xbe, 0x0d, 0x8d, 0x2e, 0x1b, 0x17, 0x7e, 0x40, 0x1d, 0x43, 0x39, 0xfc, 0x0e, 0x3f, 0x93, 0xd6,
-	0x23, 0x5a, 0xe1, 0x7d, 0x3d, 0x2e, 0x1f, 0xc4, 0x00, 0x18, 0xe7, 0xf8, 0x8c, 0x5f, 0xd0, 0xbd,
-	0x01, 0x35, 0x9f, 0x06, 0x81, 0xe5, 0xb4, 0xc5, 0x79, 0xa3, 0x2e, 0xe6, 0x4a, 0x4b, 0xa6, 0x61,
-	0x08, 0x25, 0x3f, 0x05, 0x75, 0x6e, 0x42, 0x5c, 0xf0, 0xda, 0xfe, 0x74, 0x9d, 0xbb, 0xff, 0x4d,
-	0x08, 0x87, 0x46, 0x99, 0x88, 0x11, 0x9c, 0x7c, 0x1e, 0xc6, 0xb7, 0xf8, 0xf4, 0x95, 0xaa, 0x23,
-	0xa1, 0xb3, 0xe4, 0xd2, 0x5a, 0x33, 0x96, 0x8e, 0x09, 0x2c, 0x32, 0x0f, 0x40, 0x43, 0x3b, 0x6b,
-	0x5a, 0x3f, 0x19, 0x59, 0x60, 0x31, 0x86, 0x45, 0x5e, 0x80, 0x52, 0x60, 0xfb, 0x5c, 0x27, 0x59,
-	0x8b, 0x8e, 0xa0, 0x1b, 0xab, 0x2d, 0x64, 0xe9, 0xda, 0x1f, 0x14, 0x60, 0x2a, 0x75, 0xbd, 0x8c,
-	0x65, 0xe9, 0x79, 0xb6, 0x5c, 0x46, 0xc2, 0x2c, 0x9b, 0xb8, 0x8a, 0x2c, 0x9d, 0xbc, 0x2b, 0xc5,
-	0xf2, 0x62, 0xce, 0x18, 0x38, 0xf7, 0xf4, 0xc0, 0x67, 0x72, 0x78, 0x9f, 0x44, 0xce, 0xcd, 0xb6,
-	0x51, 0x79, 0xe4, 0x3e, 0x10, 0x33, 0xdb, 0x46, 0x30, 0x4c, 0x60, 0xa6, 0x14, 0xb8, 0xe5, 0xe3,
-	0x28, 0x70, 0xb5, 0x5f, 0x2c, 0xc6, 0x5a, 0x40, 0x4a, 0xf6, 0x4f, 0x69, 0x81, 0x97, 0xd9, 0x06,
-	0x1a, 0x6e, 0xee, 0xf5, 0xf8, 0xfe, 0xc7, 0x37, 0x63, 0x09, 0x25, 0x0f, 0x45, 0xdb, 0x97, 0x72,
-	0x06, 0x01, 0xd8, 0x58, 0x6d, 0x09, 0x6f, 0x39, 0xd5, 0x6b, 0x61, 0x17, 0x94, 0x4f, 0xa9, 0x0b,
-	0xb4, 0x7f, 0x5a, 0x82, 0xc6, 0x1d, 0x77, 0xeb, 0x63, 0xe2, 0x11, 0x9f, 0xbd, 0x4d, 0x15, 0x3f,
-	0xc2, 0x6d, 0x6a, 0x13, 0x9e, 0x0b, 0x02, 0xbb, 0x45, 0x0d, 0xd7, 0x31, 0xfd, 0x85, 0xed, 0x80,
-	0x7a, 0xcb, 0x96, 0x63, 0xf9, 0x3b, 0xd4, 0x94, 0xe6, 0xc1, 0x4f, 0x1f, 0x1e, 0xcc, 0x3c, 0xb7,
-	0xb1, 0xb1, 0x9a, 0x85, 0x82, 0x83, 0xf2, 0xf2, 0x65, 0x43, 0xdc, 0x05, 0xe6, 0x77, 0xe5, 0xa4,
-	0x0f, 0x95, 0x58, 0x36, 0x62, 0xe9, 0x98, 0xc0, 0xd2, 0xfe, 0x7d, 0x11, 0xea, 0x61, 0x74, 0x13,
-	0xf2, 0x12, 0x54, 0xb7, 0x3c, 0x77, 0x97, 0x7a, 0xc2, 0x12, 0x2b, 0xef, 0xca, 0x35, 0x45, 0x12,
-	0x2a, 0x18, 0x79, 0x11, 0x2a, 0x81, 0xdb, 0xb5, 0x8c, 0xb4, 0x42, 0x6d, 0x83, 0x25, 0xa2, 0x80,
-	0xf1, 0x89, 0xc0, 0xdd, 0x44, 0x79, 0xad, 0x6a, 0xb1, 0x89, 0xc0, 0x53, 0x51, 0x42, 0xd5, 0x44,
-	0x28, 0x8f, 0x7c, 0x22, 0xbc, 0x1c, 0x8a, 0x80, 0x95, 0xe4, 0x4c, 0x4c, 0x09, 0x6d, 0xef, 0x40,
-	0xd9, 0xd7, 0x7d, 0x5b, 0x6e, 0x6f, 0x39, 0x62, 0x7d, 0x2c, 0xb4, 0x56, 0x65, 0xac, 0x8f, 0x85,
-	0xd6, 0x2a, 0x72, 0xa2, 0xda, 0xaf, 0x97, 0xa0, 0x21, 0xda, 0x57, 0xac, 0x1e, 0xa3, 0x6c, 0xe1,
-	0x37, 0xb8, 0x0b, 0x8d, 0xdf, 0xeb, 0x50, 0x8f, 0xab, 0xa3, 0xe4, 0x62, 0x18, 0xb7, 0x0b, 0x45,
-	0xc0, 0xd0, 0x8d, 0x26, 0x4a, 0xfa, 0xc3, 0xdd, 0xf4, 0x6c, 0xab, 0xe0, 0x11, 0x7a, 0xa4, 0x8c,
-	0x2b, 0x3d, 0x63, 0xc3, 0xad, 0xe2, 0x6e, 0x0c, 0x86, 0x09, 0x4c, 0xed, 0x77, 0x8b, 0x50, 0x5f,
-	0xb5, 0xb6, 0xa9, 0xb1, 0x6f, 0xd8, 0x94, 0x7c, 0x13, 0xae, 0x9a, 0xd4, 0xa6, 0x6c, 0xc7, 0xbc,
-	0xe5, 0xe9, 0x06, 0x5d, 0xa7, 0x9e, 0xc5, 0x23, 0x8c, 0xb1, 0x39, 0x28, 0x1d, 0x96, 0xaf, 0x1d,
-	0x1e, 0xcc, 0x5c, 0x5d, 0x1a, 0x88, 0x85, 0x47, 0x50, 0x20, 0x2b, 0x30, 0x6e, 0x52, 0xdf, 0xf2,
-	0xa8, 0xb9, 0x1e, 0x3b, 0x10, 0xbd, 0xa4, 0xca, 0xb9, 0x14, 0x83, 0x3d, 0x39, 0x98, 0x99, 0x50,
-	0x8a, 0x50, 0x71, 0x32, 0x4a, 0x64, 0x65, 0x4b, 0x4b, 0x57, 0xef, 0xf9, 0x34, 0xa3, 0x9c, 0x25,
-	0x5e, 0x4e, 0xbe, 0xb4, 0xac, 0x67, 0xa3, 0xe0, 0xa0, 0xbc, 0x64, 0x0b, 0xa6, 0x79, 0xf9, 0xb3,
-	0xe8, 0x96, 0x39, 0xdd, 0x97, 0x0f, 0x0f, 0x66, 0xb4, 0x25, 0xda, 0xf5, 0xa8, 0xa1, 0x07, 0xd4,
-	0x5c, 0x1a, 0x80, 0x8d, 0x03, 0xe9, 0x68, 0x15, 0x28, 0xad, 0xba, 0x6d, 0xed, 0x7b, 0x25, 0x08,
-	0x43, 0xde, 0x91, 0x3f, 0x57, 0x80, 0x86, 0xee, 0x38, 0x6e, 0x20, 0xc3, 0xc9, 0x09, 0xef, 0x10,
-	0xcc, 0x1d, 0x59, 0x6f, 0x76, 0x21, 0x22, 0x2a, 0x1c, 0x0b, 0x42, 0x67, 0x87, 0x18, 0x04, 0xe3,
-	0xbc, 0x49, 0x2f, 0xe5, 0xeb, 0xb0, 0x96, 0xbf, 0x14, 0xc7, 0xf0, 0x6c, 0xb8, 0xfa, 0x55, 0x38,
-	0x97, 0x2e, 0xec, 0x49, 0x4c, 0x95, 0xb9, 0x9c, 0x46, 0x8a, 0x00, 0x91, 0xbf, 0xd3, 0x19, 0x28,
-	0xe4, 0xac, 0x84, 0x42, 0x6e, 0xf8, 0x90, 0x20, 0x51, 0xa1, 0x07, 0x2a, 0xe1, 0xde, 0x4f, 0x29,
-	0xe1, 0x56, 0x46, 0xc1, 0xec, 0x68, 0xc5, 0xdb, 0x16, 0x5c, 0x88, 0x70, 0xa3, 0xd5, 0xe5, 0x6e,
-	0x6a, 0xf6, 0x0b, 0xb9, 0xf2, 0xb3, 0x03, 0x66, 0xff, 0x54, 0xcc, 0x01, 0xad, 0x7f, 0xfe, 0x6b,
-	0x7f, 0xab, 0x00, 0xe7, 0xe2, 0x4c, 0x78, 0x4c, 0x81, 0x2f, 0xc0, 0x84, 0x47, 0x75, 0xb3, 0xa9,
-	0x07, 0xc6, 0x0e, 0xbf, 0xea, 0x50, 0xe0, 0x77, 0x13, 0xf8, 0xed, 0x47, 0x8c, 0x03, 0x30, 0x89,
-	0x47, 0x74, 0x68, 0xb0, 0x04, 0x19, 0xcf, 0x64, 0x48, 0x2d, 0x33, 0x3f, 0xe0, 0x61, 0x44, 0x06,
-	0xe3, 0x34, 0xb5, 0x1f, 0x15, 0x60, 0x32, 0x5e, 0xe0, 0x53, 0xd7, 0x40, 0xee, 0x24, 0x35, 0x90,
-	0x8b, 0x23, 0xe8, 0xf7, 0x01, 0x5a, 0xc7, 0xef, 0x34, 0xe2, 0x55, 0xe3, 0x9a, 0xc6, 0xb8, 0x72,
-	0xa5, 0x70, 0xa4, 0x72, 0xe5, 0xe3, 0x1f, 0xe4, 0x6c, 0xd0, 0xa9, 0xa0, 0xfc, 0x0c, 0x9f, 0x0a,
-	0x3e, 0xca, 0x48, 0x69, 0xb1, 0x68, 0x5f, 0x63, 0x39, 0xa2, 0x7d, 0x75, 0xc2, 0x68, 0x5f, 0xd5,
-	0x91, 0x2d, 0x6c, 0xc7, 0x89, 0xf8, 0x55, 0x3b, 0xd3, 0x88, 0x5f, 0xf5, 0xd3, 0x8a, 0xf8, 0x05,
-	0x79, 0x23, 0x7e, 0x7d, 0xb7, 0x00, 0x93, 0x66, 0xe2, 0xc6, 0xb8, 0x8c, 0xd5, 0x30, 0xfc, 0x76,
-	0x96, 0xbc, 0x80, 0x2e, 0xae, 0x0c, 0x26, 0xd3, 0x30, 0xc5, 0x32, 0x2b, 0xce, 0xd6, 0xf8, 0x47,
-	0x12, 0x67, 0x8b, 0x7c, 0x0b, 0xea, 0xb6, 0xda, 0xeb, 0x64, 0x88, 0xd3, 0xd5, 0x91, 0x0c, 0x49,
-	0x49, 0x33, 0xba, 0x95, 0x12, 0x26, 0x61, 0xc4, 0x51, 0xfb, 0x5f, 0xd5, 0xf8, 0x86, 0x78, 0xd6,
-	0x36, 0x8e, 0xd7, 0x92, 0x36, 0x8e, 0xeb, 0x69, 0x1b, 0x47, 0xdf, 0x6e, 0x2e, 0xed, 0x1c, 0x9f,
-	0x8b, 0xed, 0x13, 0x25, 0x1e, 0x74, 0x2b, 0x1c, 0x72, 0x19, 0x7b, 0xc5, 0x02, 0x4c, 0x49, 0x21,
-	0x40, 0x01, 0xf9, 0x22, 0x3b, 0x11, 0x79, 0x19, 0x2e, 0x25, 0xc1, 0x98, 0xc6, 0x67, 0x0c, 0x7d,
-	0x15, 0x4c, 0x5a, 0x9c, 0xd8, 0xa2, 0x31, 0xae, 0x02, 0x3d, 0x87, 0x18, 0xec, 0x74, 0xe7, 0x51,
-	0xdd, 0x97, 0x96, 0x8a, 0xd8, 0xe9, 0x0e, 0x79, 0x2a, 0x4a, 0x68, 0xdc, 0x5c, 0x53, 0x7d, 0x8a,
-	0xb9, 0x46, 0x87, 0x86, 0xad, 0xfb, 0x81, 0x18, 0x4c, 0xa6, 0x5c, 0x4d, 0xfe, 0xd8, 0xf1, 0xf6,
-	0x7d, 0x26, 0x4b, 0x44, 0x02, 0xfc, 0x6a, 0x44, 0x06, 0xe3, 0x34, 0x89, 0x09, 0xe3, 0xec, 0x97,
-	0xaf, 0x2c, 0xe6, 0x42, 0x20, 0xa3, 0x21, 0x9e, 0x84, 0x47, 0x78, 0x74, 0x5c, 0x8d, 0xd1, 0xc1,
-	0x04, 0xd5, 0x01, 0x16, 0x1d, 0x18, 0xc6, 0xa2, 0x43, 0xbe, 0x2c, 0x04, 0xb7, 0xfd, 0xb0, 0x5b,
-	0x1b, 0xbc, 0x5b, 0x43, 0x0f, 0x65, 0x8c, 0x03, 0x31, 0x89, 0xcb, 0x46, 0x45, 0x4f, 0x36, 0x83,
-	0xca, 0x3e, 0x9e, 0x1c, 0x15, 0x9b, 0x49, 0x30, 0xa6, 0xf1, 0xc9, 0x3a, 0x5c, 0x0c, 0x93, 0xe2,
-	0xc5, 0x98, 0xe0, 0x74, 0x42, 0x97, 0xd1, 0xcd, 0x0c, 0x1c, 0xcc, 0xcc, 0xc9, 0xef, 0x60, 0xf5,
-	0x3c, 0x8f, 0x3a, 0xc1, 0x6d, 0xdd, 0xdf, 0x91, 0xbe, 0xa7, 0xd1, 0x1d, 0xac, 0x08, 0x84, 0x71,
-	0x3c, 0x32, 0x0f, 0x20, 0xc8, 0xf1, 0x5c, 0x53, 0x49, 0xf7, 0xee, 0xcd, 0x10, 0x82, 0x31, 0x2c,
-	0xed, 0xbb, 0x75, 0x68, 0xdc, 0xd3, 0x03, 0x6b, 0x8f, 0x72, 0xf3, 0xeb, 0xe9, 0xd8, 0xc0, 0xfe,
-	0x5a, 0x01, 0x2e, 0x27, 0x7d, 0xa6, 0x4f, 0xd1, 0x10, 0xc6, 0x63, 0x76, 0x61, 0x26, 0x37, 0x1c,
-	0x50, 0x0a, 0x6e, 0x12, 0xeb, 0x73, 0xc1, 0x3e, 0x6d, 0x93, 0x58, 0x6b, 0x10, 0x43, 0x1c, 0x5c,
-	0x96, 0x8f, 0x8b, 0x49, 0xec, 0xd9, 0x0e, 0x68, 0x9b, 0x32, 0xd8, 0x55, 0x9f, 0x19, 0x83, 0x5d,
-	0xed, 0x99, 0x90, 0xfa, 0xbb, 0x31, 0x83, 0x5d, 0x3d, 0xa7, 0xe3, 0x98, 0xbc, 0x66, 0x24, 0xa8,
-	0x0d, 0x32, 0xfc, 0xf1, 0x08, 0x21, 0xca, 0x90, 0xc2, 0x84, 0xe5, 0x2d, 0xdd, 0xb7, 0x0c, 0x29,
-	0x76, 0xe4, 0x88, 0x12, 0xae, 0x82, 0x6d, 0x0a, 0xff, 0x12, 0xfe, 0x8b, 0x82, 0x76, 0x14, 0x5b,
-	0xb4, 0x98, 0x2b, 0xb6, 0x28, 0x59, 0x84, 0xb2, 0xb3, 0x4b, 0xf7, 0x4f, 0x16, 0x6b, 0x83, 0x1f,
-	0x02, 0xef, 0xdd, 0xa5, 0xfb, 0xc8, 0x33, 0x6b, 0xdf, 0x2f, 0x02, 0xb0, 0xea, 0x1f, 0xcf, 0x74,
-	0xf6, 0x93, 0x50, 0xf5, 0x7b, 0x5c, 0x31, 0x24, 0x05, 0xa6, 0xc8, 0xdb, 0x4e, 0x24, 0xa3, 0x82,
-	0x93, 0x17, 0xa1, 0xf2, 0x7e, 0x8f, 0xf6, 0x94, 0x1f, 0x48, 0x78, 0x6e, 0x78, 0x8b, 0x25, 0xa2,
-	0x80, 0x9d, 0x9e, 0x7a, 0x5b, 0x99, 0xd8, 0x2a, 0xa7, 0x65, 0x62, 0xab, 0x43, 0xf5, 0x9e, 0xcb,
-	0x9d, 0x77, 0xb5, 0xff, 0x52, 0x04, 0x88, 0x9c, 0x23, 0xc9, 0x2f, 0x17, 0xe0, 0x52, 0x38, 0xe1,
-	0x02, 0x71, 0xfc, 0xe3, 0x81, 0xf9, 0x73, 0x9b, 0xdb, 0xb2, 0x26, 0x3b, 0x5f, 0x81, 0xd6, 0xb3,
-	0xd8, 0x61, 0x76, 0x29, 0x08, 0x42, 0x8d, 0x76, 0xba, 0xc1, 0xfe, 0x92, 0xe5, 0xc9, 0x11, 0x98,
-	0xe9, 0x83, 0x7b, 0x53, 0xe2, 0x88, 0xac, 0x52, 0x47, 0xc1, 0x27, 0x91, 0x82, 0x60, 0x48, 0x87,
-	0xec, 0x40, 0xcd, 0x71, 0xdf, 0xf5, 0x59, 0x73, 0xc8, 0xe1, 0xf8, 0xe6, 0xf0, 0x4d, 0x2e, 0x9a,
-	0x55, 0x98, 0x5d, 0xe4, 0x0f, 0x56, 0x1d, 0xd9, 0xd8, 0xbf, 0x54, 0x84, 0x0b, 0x19, 0xed, 0x40,
-	0xde, 0x84, 0x73, 0xd2, 0x0f, 0x35, 0x7a, 0xa1, 0xa2, 0x10, 0xbd, 0x50, 0xd1, 0x4a, 0xc1, 0xb0,
-	0x0f, 0x9b, 0xbc, 0x0b, 0xa0, 0x1b, 0x06, 0xf5, 0xfd, 0x35, 0xd7, 0x54, 0xe7, 0x81, 0x37, 0x98,
-	0xf8, 0xb2, 0x10, 0xa6, 0x3e, 0x39, 0x98, 0xf9, 0xe9, 0x2c, 0xd7, 0xf2, 0x54, 0x3b, 0x47, 0x19,
-	0x30, 0x46, 0x92, 0x7c, 0x13, 0x40, 0xe8, 0x00, 0xc2, 0x68, 0x26, 0x4f, 0x51, 0x9c, 0xcd, 0xaa,
-	0x60, 0x79, 0xb3, 0x6f, 0xf5, 0x74, 0x27, 0xb0, 0x82, 0x7d, 0x11, 0x3c, 0xea, 0x41, 0x48, 0x05,
-	0x63, 0x14, 0xb5, 0xdf, 0x2c, 0x42, 0x4d, 0x99, 0x1e, 0xce, 0x40, 0x17, 0xdc, 0x4e, 0xe8, 0x82,
-	0x47, 0xe4, 0x4c, 0x9e, 0xa5, 0x09, 0x76, 0x53, 0x9a, 0xe0, 0x5b, 0xf9, 0x59, 0x1d, 0xad, 0x07,
-	0xfe, 0xb5, 0x22, 0x4c, 0x2a, 0xd4, 0xbc, 0x1a, 0xda, 0xaf, 0xc0, 0x94, 0x70, 0x02, 0x59, 0xd3,
-	0x1f, 0x8b, 0x38, 0x5a, 0xbc, 0xc1, 0xca, 0xc2, 0x7f, 0xbb, 0x99, 0x04, 0x61, 0x1a, 0x97, 0x0d,
-	0x6b, 0x91, 0xb4, 0xc9, 0x0e, 0x61, 0xc2, 0x6c, 0x2c, 0xce, 0x9b, 0x7c, 0x58, 0x37, 0x53, 0x30,
-	0xec, 0xc3, 0x4e, 0xab, 0x88, 0xcb, 0xa7, 0xa0, 0x22, 0xfe, 0xed, 0x02, 0x8c, 0x47, 0xed, 0x75,
-	0xea, 0x0a, 0xe2, 0xed, 0xa4, 0x82, 0x78, 0x21, 0xf7, 0x70, 0x18, 0xa0, 0x1e, 0xfe, 0x8b, 0x55,
-	0x48, 0xdc, 0x69, 0x20, 0x5b, 0x70, 0xd5, 0xca, 0xf4, 0xcc, 0x8c, 0xad, 0x36, 0x61, 0xd0, 0x85,
-	0x95, 0x81, 0x98, 0x78, 0x04, 0x15, 0xd2, 0x83, 0xda, 0x1e, 0xf5, 0x02, 0xcb, 0xa0, 0xaa, 0x7e,
-	0xb7, 0x72, 0x8b, 0x64, 0x52, 0x09, 0x1e, 0xb6, 0xe9, 0x03, 0xc9, 0x00, 0x43, 0x56, 0x64, 0x0b,
-	0x2a, 0xd4, 0x6c, 0x53, 0x15, 0xd9, 0x2c, 0x67, 0xa4, 0xe9, 0xb0, 0x3d, 0xd9, 0x9f, 0x8f, 0x82,
-	0x34, 0xf1, 0xe3, 0x8a, 0xa6, 0x72, 0x4e, 0x01, 0xeb, 0x98, 0xea, 0x25, 0xb2, 0x1b, 0x6a, 0x5b,
-	0x2b, 0x23, 0x5a, 0x3c, 0x8e, 0xd0, 0xb5, 0xfa, 0x50, 0x7f, 0xa4, 0x07, 0xd4, 0xeb, 0xe8, 0xde,
-	0xae, 0x3c, 0x6d, 0x0c, 0x5f, 0xc3, 0x87, 0x8a, 0x52, 0x54, 0xc3, 0x30, 0x09, 0x23, 0x3e, 0xc4,
-	0x85, 0x7a, 0x20, 0xc5, 0x67, 0xa5, 0x52, 0x1e, 0x9e, 0xa9, 0x12, 0xc4, 0x7d, 0x79, 0xb7, 0x41,
-	0xfd, 0x62, 0xc4, 0x83, 0xec, 0x25, 0x9e, 0x40, 0x10, 0x0f, 0x5f, 0xe4, 0x78, 0x7d, 0x46, 0x91,
-	0x8a, 0xb6, 0x9b, 0xec, 0xa7, 0x14, 0xb4, 0xff, 0x51, 0x89, 0x96, 0xe5, 0xb3, 0xd6, 0x13, 0x7e,
-	0x3e, 0xa9, 0x27, 0xbc, 0x96, 0xd6, 0x13, 0xa6, 0x6c, 0xfe, 0x27, 0xf7, 0x86, 0x4e, 0xa9, 0xd7,
-	0xca, 0xa7, 0xa0, 0x5e, 0x7b, 0x05, 0x1a, 0x7b, 0x7c, 0x25, 0x10, 0x61, 0xd2, 0x2a, 0x7c, 0x1b,
-	0xe1, 0x2b, 0xfb, 0x83, 0x28, 0x19, 0xe3, 0x38, 0x2c, 0x8b, 0x7c, 0x59, 0x2a, 0x8c, 0x4c, 0x2e,
-	0xb3, 0xb4, 0xa2, 0x64, 0x8c, 0xe3, 0x70, 0x47, 0x4a, 0xcb, 0xd9, 0x15, 0x19, 0xaa, 0x3c, 0x83,
-	0x70, 0xa4, 0x54, 0x89, 0x18, 0xc1, 0xc9, 0x0d, 0xa8, 0xf5, 0xcc, 0x6d, 0x81, 0x5b, 0xe3, 0xb8,
-	0x5c, 0xc2, 0xdc, 0x5c, 0x5a, 0x96, 0x61, 0xdb, 0x14, 0x94, 0x95, 0xa4, 0xa3, 0x77, 0x15, 0x80,
-	0x9f, 0x0d, 0x65, 0x49, 0xd6, 0xa2, 0x64, 0x8c, 0xe3, 0x90, 0x2f, 0xc1, 0xa4, 0x47, 0xcd, 0x9e,
-	0x41, 0xc3, 0x5c, 0xc0, 0x73, 0xc9, 0x78, 0xb6, 0x71, 0x08, 0xa6, 0x30, 0x07, 0x28, 0x09, 0x1b,
-	0x43, 0x29, 0x09, 0xbf, 0x0a, 0x93, 0xa6, 0xa7, 0x5b, 0x0e, 0x35, 0xef, 0x3b, 0xdc, 0xb1, 0x43,
-	0xba, 0x73, 0x86, 0x0a, 0xfa, 0xa5, 0x04, 0x14, 0x53, 0xd8, 0xda, 0x3f, 0x2b, 0x42, 0x45, 0x44,
-	0xd9, 0x5d, 0x81, 0x0b, 0x96, 0x63, 0x05, 0x96, 0x6e, 0x2f, 0x51, 0x5b, 0xdf, 0x8f, 0x3b, 0xb8,
-	0x54, 0x9a, 0xcf, 0xb1, 0x83, 0xf6, 0x4a, 0x3f, 0x18, 0xb3, 0xf2, 0xb0, 0xc6, 0x91, 0x8f, 0x5f,
-	0x28, 0x2a, 0x42, 0x8f, 0x26, 0x42, 0xbc, 0x27, 0x20, 0x98, 0xc2, 0x64, 0xc2, 0x50, 0xb7, 0xcf,
-	0x73, 0xa5, 0x22, 0x84, 0xa1, 0xa4, 0x33, 0x49, 0x12, 0x8f, 0x0b, 0xe9, 0x3d, 0x2e, 0x10, 0x87,
-	0x97, 0xa6, 0xa4, 0x13, 0x9c, 0x10, 0xd2, 0x53, 0x30, 0xec, 0xc3, 0x66, 0x14, 0xb6, 0x75, 0xcb,
-	0xee, 0x79, 0x34, 0xa2, 0x50, 0x89, 0x28, 0x2c, 0xa7, 0x60, 0xd8, 0x87, 0xad, 0x6d, 0x00, 0xac,
-	0xf7, 0x6c, 0x5f, 0xe7, 0x11, 0x95, 0x46, 0xf6, 0xce, 0xc7, 0xef, 0x17, 0x61, 0x5c, 0x90, 0x95,
-	0x07, 0xe9, 0x79, 0x00, 0x19, 0xb8, 0xc9, 0x34, 0x3d, 0x29, 0x1b, 0x44, 0x0b, 0x5c, 0x08, 0xc1,
-	0x18, 0xd6, 0xf1, 0x5c, 0xca, 0x5e, 0x87, 0x71, 0xe5, 0x22, 0xc6, 0xc5, 0x8e, 0x94, 0x7b, 0xed,
-	0x62, 0x0c, 0x86, 0x09, 0x4c, 0xb2, 0xc4, 0x5a, 0x7f, 0x4b, 0x04, 0x0a, 0xb0, 0x5c, 0x87, 0xe7,
-	0x16, 0x11, 0x35, 0xc2, 0xab, 0x95, 0xad, 0x14, 0x1c, 0xfb, 0x72, 0x90, 0xcf, 0x41, 0xad, 0xa3,
-	0x3f, 0xde, 0x74, 0x74, 0x63, 0x57, 0x2e, 0x21, 0xa1, 0x5c, 0xb1, 0x26, 0xd3, 0x31, 0xc4, 0x20,
-	0xba, 0x3c, 0x87, 0x8f, 0xe5, 0xbd, 0x7c, 0x18, 0x76, 0x59, 0xdf, 0x49, 0xfc, 0xbf, 0x15, 0x80,
-	0xf4, 0xdf, 0xeb, 0x21, 0x3b, 0x30, 0xe6, 0x70, 0xe5, 0x72, 0xee, 0xa7, 0x42, 0x62, 0x3a, 0x6a,
-	0xb1, 0xeb, 0xcb, 0x04, 0x49, 0x9f, 0x38, 0x50, 0xa3, 0x8f, 0x03, 0xea, 0x39, 0xe1, 0x3d, 0xbf,
-	0xd1, 0x3c, 0x4b, 0x22, 0x0e, 0xdb, 0x92, 0x32, 0x86, 0x3c, 0xb4, 0xdf, 0x2b, 0x42, 0x23, 0x86,
-	0xf7, 0x34, 0x9d, 0x0d, 0x0f, 0x1d, 0x23, 0x74, 0xba, 0x9b, 0x9e, 0x2d, 0xc7, 0x56, 0x2c, 0x74,
-	0x8c, 0x04, 0xe1, 0x2a, 0xc6, 0xf1, 0xd8, 0x00, 0xee, 0xe8, 0x7e, 0x90, 0x18, 0x65, 0xe1, 0x00,
-	0x5e, 0x0b, 0x21, 0x18, 0xc3, 0x22, 0xd7, 0xe5, 0xc3, 0x32, 0xe5, 0x64, 0x80, 0xdd, 0x01, 0xaf,
-	0xc6, 0x54, 0x46, 0xf0, 0x6a, 0x0c, 0x69, 0xc3, 0x39, 0x55, 0x6a, 0x05, 0x3d, 0x59, 0xf8, 0x55,
-	0xb1, 0xf2, 0xa4, 0x48, 0x60, 0x1f, 0x51, 0xed, 0xfb, 0x05, 0x98, 0x48, 0x68, 0x14, 0x45, 0x68,
-	0x5c, 0x75, 0x2b, 0x2d, 0x11, 0x1a, 0x37, 0x76, 0x99, 0xec, 0x65, 0x18, 0x13, 0x0d, 0x94, 0x76,
-	0x36, 0x17, 0x4d, 0x88, 0x12, 0xca, 0x44, 0x05, 0x69, 0xb3, 0x48, 0x8b, 0x0a, 0xd2, 0xa8, 0x81,
-	0x0a, 0x2e, 0x4c, 0x81, 0xa2, 0x74, 0xb2, 0xa5, 0x63, 0xa6, 0x40, 0x91, 0x8e, 0x21, 0x86, 0xf6,
-	0x0f, 0x78, 0xb9, 0x03, 0x6f, 0x3f, 0x54, 0x95, 0xb4, 0xa1, 0x2a, 0x1d, 0x8c, 0xe5, 0xd4, 0x78,
-	0x33, 0x87, 0x9a, 0x93, 0xd3, 0x91, 0x2e, 0xb2, 0xba, 0xb1, 0x7b, 0x7f, 0x7b, 0x1b, 0x15, 0x75,
-	0x72, 0x13, 0xea, 0xae, 0x23, 0x97, 0x64, 0x59, 0xfd, 0xcf, 0x32, 0x51, 0xe0, 0xbe, 0x4a, 0x7c,
-	0x72, 0x30, 0x73, 0x39, 0xfc, 0x49, 0x14, 0x12, 0xa3, 0x9c, 0xda, 0x9f, 0x29, 0xc0, 0x25, 0x74,
-	0x6d, 0xdb, 0x72, 0xda, 0x49, 0x53, 0x36, 0xb1, 0x61, 0x52, 0xac, 0x34, 0x7b, 0xba, 0x65, 0xeb,
-	0x5b, 0x36, 0x7d, 0xaa, 0xaa, 0xa3, 0x17, 0x58, 0xf6, 0xac, 0x78, 0x39, 0x78, 0x76, 0xc5, 0x09,
-	0xee, 0x7b, 0xad, 0xc0, 0xb3, 0x9c, 0xb6, 0xd8, 0xf6, 0xd6, 0x12, 0xb4, 0x30, 0x45, 0x5b, 0xfb,
-	0x77, 0x65, 0xe0, 0xce, 0xab, 0xe4, 0x0b, 0x50, 0xef, 0x50, 0x63, 0x47, 0x77, 0x2c, 0x5f, 0x05,
-	0x19, 0xbf, 0xc2, 0xea, 0xb5, 0xa6, 0x12, 0x9f, 0xb0, 0xae, 0x58, 0x68, 0xad, 0xf2, 0x7b, 0x64,
-	0x11, 0x2e, 0x31, 0x60, 0xac, 0xed, 0xfb, 0x7a, 0xd7, 0xca, 0xed, 0x33, 0x24, 0x82, 0x3a, 0x8b,
-	0xe5, 0x48, 0x7c, 0xa3, 0x24, 0x4d, 0x0c, 0xa8, 0x74, 0x6d, 0xdd, 0x72, 0x72, 0xbf, 0x74, 0xc9,
-	0x6a, 0xb0, 0xce, 0x28, 0x89, 0xfd, 0x8e, 0x7f, 0xa2, 0xa0, 0x4d, 0x7a, 0xd0, 0xf0, 0x0d, 0x4f,
-	0xef, 0xf8, 0x3b, 0xfa, 0xfc, 0xab, 0xaf, 0xe5, 0x3e, 0xcd, 0x45, 0xac, 0x84, 0x70, 0xb9, 0x88,
-	0x0b, 0x6b, 0xad, 0xdb, 0x0b, 0xf3, 0xaf, 0xbe, 0x86, 0x71, 0x3e, 0x71, 0xb6, 0xaf, 0xbe, 0x32,
-	0x9f, 0xff, 0xe5, 0xcb, 0x6c, 0xb6, 0xaf, 0xbe, 0x32, 0x8f, 0x71, 0x3e, 0xac, 0x49, 0xdd, 0xd8,
-	0x36, 0x96, 0x8f, 0xe1, 0xfd, 0xc8, 0x2c, 0xc0, 0x3f, 0x51, 0xd0, 0xd6, 0xfe, 0x67, 0x01, 0xea,
-	0x21, 0x9c, 0x2d, 0x94, 0x22, 0x5c, 0xe5, 0xca, 0xd2, 0xc9, 0x64, 0x13, 0xbe, 0x50, 0x2e, 0xca,
-	0xac, 0x18, 0x12, 0x21, 0xef, 0xc0, 0xb8, 0xf8, 0x96, 0xe1, 0xa3, 0x8b, 0x27, 0x8e, 0x51, 0xbd,
-	0x18, 0xcb, 0x8e, 0x09, 0x62, 0xe4, 0xcb, 0x30, 0xc1, 0xe5, 0xa0, 0x9b, 0x8e, 0xd9, 0x75, 0x2d,
-	0xf9, 0xda, 0x53, 0x2c, 0x52, 0xd7, 0x46, 0x1c, 0x88, 0x49, 0xdc, 0xb0, 0xe2, 0xbc, 0x27, 0xc8,
-	0x26, 0x00, 0xdb, 0x29, 0x64, 0x29, 0x4f, 0x54, 0x75, 0xae, 0x1c, 0xdd, 0x0c, 0x33, 0x63, 0x8c,
-	0x50, 0x46, 0x14, 0xf0, 0xe2, 0xa8, 0xa3, 0x80, 0xcf, 0x41, 0x7d, 0x47, 0x77, 0x4c, 0x7f, 0x47,
-	0xdf, 0xa5, 0xf2, 0x46, 0x45, 0x78, 0x72, 0xbf, 0xad, 0x00, 0x18, 0xe1, 0x68, 0xff, 0x68, 0x0c,
-	0x84, 0x1b, 0x15, 0x5b, 0xd2, 0x4d, 0xcb, 0x17, 0xf7, 0x9e, 0x0a, 0x3c, 0x67, 0xb8, 0xa4, 0x2f,
-	0xc9, 0x74, 0x0c, 0x31, 0xc8, 0x15, 0x28, 0x75, 0x2c, 0x47, 0x0a, 0xec, 0xdc, 0xee, 0xb1, 0x66,
-	0x39, 0xc8, 0xd2, 0x38, 0x48, 0x7f, 0x2c, 0x05, 0x72, 0x01, 0xd2, 0x1f, 0x23, 0x4b, 0x23, 0x5f,
-	0x81, 0x29, 0xdb, 0x75, 0x77, 0xd9, 0xe2, 0x1c, 0xf7, 0x0c, 0x9f, 0x10, 0x9a, 0xc8, 0xd5, 0x24,
-	0x08, 0xd3, 0xb8, 0x64, 0x13, 0x9e, 0xfb, 0x80, 0x7a, 0xae, 0xdc, 0x8d, 0x5a, 0x36, 0xa5, 0x5d,
-	0x45, 0x46, 0x88, 0x81, 0xdc, 0x71, 0xfd, 0xeb, 0xd9, 0x28, 0x38, 0x28, 0x2f, 0xbf, 0x6a, 0xa3,
-	0x7b, 0x6d, 0x1a, 0xac, 0x7b, 0x2e, 0x13, 0xf5, 0x2d, 0xa7, 0xad, 0xc8, 0x8e, 0x45, 0x64, 0x37,
-	0xb2, 0x51, 0x70, 0x50, 0x5e, 0xf2, 0x36, 0x4c, 0x0b, 0x90, 0x10, 0x0a, 0x17, 0xc4, 0x22, 0x6e,
-	0xd9, 0xea, 0xf9, 0xed, 0x09, 0x61, 0x5e, 0xde, 0x18, 0x80, 0x83, 0x03, 0x73, 0x93, 0x3b, 0x70,
-	0x4e, 0x39, 0x17, 0xac, 0x53, 0xaf, 0x15, 0xba, 0xd6, 0x4d, 0xa8, 0x1b, 0x06, 0xca, 0xc3, 0x1e,
-	0x53, 0x58, 0xd8, 0x97, 0x8f, 0x20, 0x5c, 0xe6, 0xfe, 0x73, 0x9b, 0xdd, 0x45, 0xd7, 0xb5, 0x4d,
-	0xf7, 0x91, 0xa3, 0xea, 0x2e, 0xce, 0xb7, 0xdc, 0x9f, 0xa0, 0x95, 0x89, 0x81, 0x03, 0x72, 0xb2,
-	0x9a, 0x73, 0xc8, 0x92, 0xfb, 0xc8, 0x49, 0x53, 0x85, 0xa8, 0xe6, 0xad, 0x01, 0x38, 0x38, 0x30,
-	0x37, 0x59, 0x06, 0x92, 0xae, 0xc1, 0x66, 0x57, 0x7a, 0xbc, 0x5c, 0x16, 0xf1, 0xea, 0xd2, 0x50,
-	0xcc, 0xc8, 0x41, 0x56, 0xe1, 0x62, 0x3a, 0x95, 0xb1, 0x93, 0xce, 0x2f, 0x3c, 0x52, 0x3d, 0x66,
-	0xc0, 0x31, 0x33, 0x97, 0xd6, 0x80, 0x7a, 0xf8, 0x80, 0xb1, 0xf6, 0x6f, 0x8b, 0x30, 0x95, 0x8a,
-	0xf9, 0x75, 0x06, 0x96, 0x10, 0x27, 0x61, 0x09, 0x59, 0xcd, 0xf5, 0x10, 0x73, 0xac, 0xe4, 0x03,
-	0x0d, 0x22, 0x7b, 0x29, 0x83, 0xc8, 0xbd, 0x91, 0x71, 0x3c, 0xda, 0x2e, 0x72, 0x58, 0x80, 0x0b,
-	0xa9, 0x1c, 0x67, 0xa0, 0xee, 0xef, 0x24, 0xd5, 0xfd, 0xb7, 0x47, 0x55, 0xd9, 0x01, 0x5a, 0xff,
-	0xff, 0xd3, 0x5f, 0xc9, 0x96, 0xb0, 0x42, 0x55, 0x65, 0x78, 0xa5, 0xdc, 0x07, 0x4a, 0x15, 0xbf,
-	0x89, 0xf5, 0x6f, 0x32, 0x5c, 0x8d, 0xd3, 0x46, 0xc5, 0x85, 0xf8, 0x50, 0x53, 0x31, 0x94, 0x46,
-	0x6b, 0x63, 0x0b, 0x1b, 0x3b, 0x0c, 0x8b, 0x17, 0x32, 0xd2, 0xfe, 0x4a, 0x09, 0x2e, 0x65, 0x0e,
-	0x8a, 0xb3, 0x53, 0xb5, 0x7e, 0x39, 0xa9, 0x6a, 0x7d, 0x29, 0xad, 0x6a, 0xbd, 0x98, 0x2a, 0xdf,
-	0x33, 0xac, 0x71, 0x1d, 0xa1, 0x16, 0x51, 0x9b, 0x82, 0x89, 0x44, 0xdc, 0x2f, 0xed, 0x77, 0x2b,
-	0xd0, 0x88, 0x8d, 0xa4, 0x67, 0x2e, 0xde, 0x12, 0xf9, 0x12, 0x4c, 0x76, 0xfc, 0xf6, 0xca, 0xd2,
-	0x6d, 0xaa, 0x9b, 0xd4, 0x53, 0xd7, 0x4c, 0xeb, 0xf2, 0xac, 0x95, 0x80, 0x60, 0x0a, 0x93, 0xac,
-	0xc2, 0x25, 0x8f, 0xbe, 0xdf, 0xa3, 0x7e, 0x90, 0xd4, 0x45, 0x4a, 0x91, 0x45, 0x6e, 0x37, 0x29,
-	0x04, 0x1f, 0xb3, 0x33, 0xb1, 0x25, 0x44, 0xf8, 0x26, 0x54, 0x72, 0xce, 0x23, 0xd5, 0xde, 0xdc,
-	0x41, 0x41, 0x44, 0x67, 0x8a, 0xa5, 0xa0, 0xe0, 0x32, 0xe0, 0xea, 0xc2, 0xd8, 0x47, 0x78, 0x75,
-	0x21, 0xee, 0x2f, 0x59, 0x3d, 0xd2, 0x5f, 0xf2, 0x99, 0x76, 0x0f, 0xd3, 0xbe, 0x0d, 0x89, 0x06,
-	0x27, 0x2e, 0xd4, 0xc3, 0xca, 0xe6, 0xf6, 0xd9, 0x8a, 0xae, 0x0f, 0x70, 0x83, 0x45, 0xf8, 0x8b,
-	0x11, 0x0f, 0x6d, 0x9b, 0xcd, 0x42, 0xdf, 0xb7, 0x5c, 0xe7, 0x74, 0x1f, 0xa4, 0xfe, 0xd7, 0x45,
-	0xa8, 0x87, 0x66, 0xb0, 0x63, 0x3c, 0x5c, 0x95, 0x68, 0x88, 0xe2, 0xe9, 0x37, 0x44, 0xfc, 0x32,
-	0x4c, 0x29, 0xc7, 0x65, 0x98, 0x2e, 0x54, 0x03, 0xcf, 0x6a, 0xb7, 0xa5, 0xd2, 0x30, 0xcf, 0x6d,
-	0x98, 0xb0, 0xb9, 0x36, 0x04, 0x41, 0xd9, 0xb2, 0xe2, 0x07, 0x15, 0x1b, 0xed, 0x3d, 0x38, 0x97,
-	0xc6, 0xe4, 0x1a, 0x35, 0x63, 0x87, 0x9a, 0x3d, 0x5b, 0xb5, 0x71, 0xa4, 0x51, 0x93, 0xe9, 0x18,
-	0x62, 0xb0, 0xc9, 0xc4, 0xba, 0xe9, 0x03, 0xd7, 0x51, 0x7b, 0x14, 0x9f, 0x4c, 0x1b, 0x32, 0x0d,
-	0x43, 0xa8, 0xf6, 0x9f, 0x4b, 0x70, 0x25, 0x32, 0x66, 0xae, 0xe9, 0x8e, 0xde, 0x4e, 0x3a, 0xaa,
-	0x7e, 0x12, 0x95, 0x61, 0x24, 0xaf, 0x0f, 0x96, 0x9e, 0x81, 0xd7, 0x07, 0xff, 0x5f, 0x11, 0xf8,
-	0xe5, 0x3a, 0xf2, 0x6d, 0x18, 0x57, 0xed, 0xc9, 0xfe, 0x65, 0x77, 0xde, 0xcc, 0xdd, 0x9d, 0xfc,
-	0x0e, 0x5f, 0x68, 0xe7, 0x89, 0xa7, 0x62, 0x82, 0x21, 0x71, 0xa1, 0xb6, 0xad, 0xdb, 0x36, 0x3b,
-	0xbc, 0xe7, 0x16, 0x1c, 0x13, 0xcc, 0xf9, 0x30, 0x5f, 0x96, 0xa4, 0x31, 0x64, 0x42, 0xbe, 0x5b,
-	0x80, 0x09, 0x2f, 0xae, 0xbd, 0x95, 0x1d, 0x92, 0xc7, 0x75, 0x37, 0x46, 0x2d, 0x7e, 0x9d, 0x22,
-	0xae, 0x22, 0x4e, 0xf2, 0xd4, 0xfe, 0x53, 0x01, 0x26, 0x5a, 0xb6, 0x65, 0x5a, 0x4e, 0xfb, 0x14,
-	0x1f, 0x3f, 0xbc, 0x0f, 0x15, 0xdf, 0xb6, 0x4c, 0x3a, 0xe4, 0x5d, 0x5b, 0xf1, 0x34, 0x3f, 0x23,
-	0x80, 0x82, 0x4e, 0xf2, 0x35, 0xc5, 0xd2, 0x31, 0x5e, 0x53, 0xfc, 0xcd, 0x1a, 0xc8, 0x6b, 0xa2,
-	0xa4, 0x07, 0xf5, 0xb6, 0x7a, 0xa4, 0x4d, 0xd6, 0xf1, 0x76, 0x8e, 0x00, 0xff, 0x89, 0xe7, 0xde,
-	0xc4, 0xda, 0x1f, 0x26, 0x62, 0xc4, 0x89, 0x50, 0xa8, 0xf0, 0x60, 0x0c, 0xb9, 0xad, 0x5d, 0xb1,
-	0xb0, 0x1b, 0xa2, 0x65, 0x78, 0x02, 0x0a, 0xea, 0x44, 0x87, 0xf2, 0x4e, 0x10, 0x74, 0xe5, 0x60,
-	0x1a, 0xde, 0x76, 0x18, 0xc5, 0x24, 0x15, 0xd2, 0x2c, 0xfb, 0x47, 0x4e, 0x9a, 0xb1, 0x70, 0xf4,
-	0xf0, 0x25, 0xf9, 0xc5, 0x5c, 0x6e, 0xc2, 0x71, 0x16, 0xec, 0x1f, 0x39, 0x69, 0xf2, 0x73, 0xd0,
-	0x08, 0x3c, 0xdd, 0xf1, 0xb7, 0x5d, 0xaf, 0x43, 0x3d, 0x29, 0x81, 0x0e, 0x3f, 0x33, 0x36, 0x97,
-	0x36, 0x22, 0x6a, 0xc2, 0xe4, 0x9e, 0x48, 0xc2, 0x38, 0x37, 0xb2, 0x0b, 0xb5, 0x9e, 0x29, 0x0a,
-	0x26, 0xe5, 0xcf, 0x85, 0x1c, 0x9c, 0xe3, 0x4e, 0xc0, 0xea, 0x0f, 0x43, 0x06, 0x6c, 0x34, 0x46,
-	0x71, 0x13, 0xab, 0x39, 0x47, 0x63, 0x2a, 0xa6, 0xd3, 0xe0, 0x80, 0x89, 0xa4, 0x13, 0x1d, 0xcc,
-	0x6b, 0x39, 0x1b, 0x37, 0x71, 0xc0, 0x92, 0x51, 0x6e, 0xd3, 0xc7, 0x72, 0x0b, 0xc6, 0xba, 0xdc,
-	0x18, 0x2d, 0xef, 0x27, 0xdc, 0xcc, 0x69, 0xd3, 0x8e, 0xdf, 0xfe, 0x16, 0x29, 0x28, 0x19, 0x90,
-	0x6f, 0x40, 0xc9, 0x7f, 0xdf, 0x97, 0x8f, 0x98, 0xe7, 0x30, 0x3a, 0xbc, 0xaf, 0xc6, 0x26, 0x57,
-	0x08, 0xb7, 0xde, 0xf7, 0x91, 0xd1, 0xd5, 0x7e, 0xbb, 0x0c, 0xf5, 0x10, 0xc6, 0xd6, 0x21, 0xfd,
-	0x91, 0x8f, 0xb4, 0x1d, 0xdd, 0xbf, 0x0a, 0xd7, 0xa1, 0x85, 0x87, 0x2d, 0x01, 0xc0, 0x08, 0x87,
-	0x65, 0xe0, 0x4e, 0xfc, 0xdc, 0x3e, 0x5c, 0x4c, 0x66, 0x78, 0x4b, 0x01, 0x30, 0xc2, 0x21, 0x0f,
-	0xe0, 0x32, 0xff, 0xb9, 0xff, 0xc8, 0xa1, 0xde, 0xc2, 0xc3, 0xd6, 0x82, 0x61, 0xb8, 0x3d, 0x6e,
-	0xe0, 0x28, 0x25, 0x9c, 0xaa, 0x2e, 0xbf, 0x95, 0x89, 0x85, 0x03, 0x72, 0x93, 0x45, 0x38, 0xbf,
-	0x67, 0xf9, 0x96, 0xd0, 0xcf, 0xc6, 0xfd, 0x5c, 0x2b, 0x42, 0xb8, 0x78, 0x90, 0x06, 0x62, 0x3f,
-	0x3e, 0x59, 0x81, 0x0b, 0x1d, 0xfd, 0xf1, 0xbd, 0x5e, 0x67, 0x8b, 0x7a, 0xf7, 0xb7, 0xa5, 0xb2,
-	0xc0, 0x97, 0xbe, 0x25, 0xdc, 0xb5, 0x66, 0xad, 0x1f, 0x8c, 0x59, 0x79, 0xc8, 0x57, 0x60, 0xea,
-	0x91, 0x6e, 0xf1, 0x33, 0x68, 0x5c, 0x95, 0x5d, 0x11, 0x8a, 0xf6, 0x87, 0x49, 0x10, 0xa6, 0x71,
-	0xc9, 0x2b, 0xd0, 0xa0, 0xd2, 0x34, 0xb2, 0xe9, 0xd9, 0xea, 0x5e, 0xe6, 0xe1, 0xc1, 0x4c, 0x43,
-	0x59, 0x4c, 0xb8, 0xad, 0x3e, 0x86, 0xc3, 0x4e, 0xda, 0x7a, 0x10, 0x78, 0xd6, 0x56, 0x2f, 0xe0,
-	0x4d, 0x2d, 0xbc, 0xf2, 0xe4, 0x49, 0x7b, 0x21, 0x01, 0xc1, 0x14, 0x26, 0xb9, 0x0f, 0x97, 0xa4,
-	0x46, 0x24, 0x89, 0x28, 0x83, 0xe0, 0x71, 0x41, 0x68, 0x2d, 0x0b, 0x01, 0xb3, 0xf3, 0x69, 0x1d,
-	0x90, 0x1a, 0x1d, 0x62, 0x24, 0x5e, 0x52, 0x16, 0xa1, 0x61, 0xe6, 0x8e, 0xb7, 0x61, 0x86, 0x4f,
-	0xfa, 0xc6, 0x5e, 0x72, 0xcb, 0x7c, 0x32, 0x59, 0xfb, 0x37, 0x45, 0x28, 0x6d, 0xac, 0xb6, 0xc4,
-	0xeb, 0x2c, 0xfc, 0x6d, 0x76, 0xda, 0xda, 0xb5, 0xba, 0x0f, 0xa8, 0x67, 0x6d, 0xef, 0x4b, 0x63,
-	0x4a, 0xec, 0x75, 0x96, 0x34, 0x06, 0x66, 0xe4, 0xe2, 0xb6, 0x32, 0x7d, 0x91, 0x7a, 0x39, 0x6c,
-	0x65, 0x0b, 0x51, 0x76, 0x4c, 0x10, 0x23, 0x9b, 0x00, 0x46, 0x44, 0xba, 0x74, 0x62, 0x03, 0x57,
-	0x8c, 0x70, 0x8c, 0x10, 0x41, 0xa8, 0xef, 0x32, 0x54, 0x4e, 0xb5, 0x7c, 0x12, 0xaa, 0x7c, 0x69,
-	0xbd, 0xab, 0xf2, 0x62, 0x44, 0x46, 0x73, 0x60, 0x22, 0xf1, 0xbc, 0x32, 0xf9, 0x22, 0xd4, 0xdc,
-	0x6e, 0x4c, 0xde, 0xa8, 0xf3, 0xeb, 0x84, 0xb5, 0xfb, 0x32, 0xed, 0xc9, 0xc1, 0xcc, 0xc4, 0xaa,
-	0xdb, 0xb6, 0x0c, 0x95, 0x80, 0x21, 0x3a, 0xd1, 0x60, 0x8c, 0x07, 0xae, 0x51, 0x8f, 0x2b, 0xf3,
-	0x05, 0x8f, 0xbf, 0x7f, 0xea, 0xa3, 0x84, 0x68, 0x3f, 0x5f, 0x86, 0xc8, 0xe5, 0x94, 0xf8, 0x30,
-	0x26, 0x2e, 0xcd, 0x4b, 0xd1, 0xe6, 0x54, 0xef, 0xe7, 0x4b, 0x56, 0xa4, 0x0d, 0xa5, 0xf7, 0xdc,
-	0xad, 0xdc, 0x92, 0x4d, 0x2c, 0xfa, 0x9e, 0x98, 0xbb, 0xb1, 0x04, 0x64, 0x1c, 0xc8, 0x5f, 0x2f,
-	0xc0, 0x79, 0x3f, 0x7d, 0x36, 0x94, 0xc3, 0x01, 0xf3, 0x1f, 0x82, 0xd3, 0xa7, 0x4d, 0x79, 0xef,
-	0x73, 0x10, 0x18, 0xfb, 0xcb, 0xc2, 0xda, 0x5f, 0xf8, 0x82, 0xca, 0xe1, 0x34, 0x7c, 0xfb, 0x0b,
-	0xff, 0xd2, 0x64, 0xfb, 0x27, 0xd3, 0x50, 0xb2, 0xd2, 0xbe, 0x53, 0x84, 0x46, 0x4c, 0x9c, 0xc9,
-	0xfd, 0x66, 0xf7, 0xe3, 0xd4, 0x9b, 0xdd, 0xeb, 0xc3, 0xbb, 0x46, 0x47, 0xa5, 0x3a, 0xed, 0x67,
-	0xbb, 0xff, 0x49, 0x11, 0x4a, 0x9b, 0x4b, 0xcb, 0x67, 0xae, 0xde, 0x22, 0x3b, 0x50, 0xdd, 0xea,
-	0x59, 0x76, 0x60, 0x39, 0xb9, 0xe3, 0x83, 0xaa, 0x27, 0xce, 0xa5, 0x6f, 0x90, 0xa0, 0x8a, 0x8a,
-	0x3c, 0x69, 0x43, 0xb5, 0x2d, 0x1e, 0x68, 0xc8, 0x7d, 0x61, 0x4c, 0x3e, 0xf4, 0x20, 0x18, 0xc9,
-	0x1f, 0x54, 0xd4, 0xb5, 0x7d, 0x18, 0xdb, 0x5c, 0x92, 0xe7, 0xe2, 0x33, 0x56, 0x16, 0xfe, 0x1c,
-	0x84, 0x62, 0xf2, 0xd9, 0x33, 0xff, 0xaf, 0x05, 0x48, 0x9e, 0x0c, 0xce, 0x7e, 0x34, 0xed, 0xa6,
-	0x47, 0xd3, 0xd2, 0x28, 0x26, 0x5f, 0xf6, 0x80, 0xd2, 0xfe, 0x55, 0x01, 0x52, 0x91, 0x4e, 0xc8,
-	0x6b, 0x32, 0xd6, 0x77, 0xf2, 0x66, 0x8e, 0x8a, 0xf5, 0x4d, 0x92, 0xd8, 0xb1, 0x98, 0xdf, 0x1f,
-	0x16, 0x60, 0xc2, 0x8b, 0x3b, 0x9c, 0xc9, 0xe2, 0x0f, 0x6f, 0x67, 0xcd, 0x74, 0x5f, 0x93, 0xb7,
-	0xc7, 0xe2, 0x20, 0x4c, 0xf2, 0xd5, 0xfe, 0x61, 0x11, 0xc6, 0xce, 0x2c, 0xb8, 0x1b, 0x4d, 0x98,
-	0xb1, 0x17, 0x73, 0xae, 0xf6, 0x03, 0xad, 0xd7, 0x9d, 0x94, 0xf5, 0xfa, 0x66, 0x5e, 0x46, 0x47,
-	0x1b, 0xad, 0xff, 0x45, 0x01, 0xe4, 0x5e, 0xb3, 0xe2, 0xf8, 0x81, 0xee, 0x18, 0x94, 0x18, 0xe1,
-	0xc6, 0x96, 0xd7, 0x94, 0x29, 0x6f, 0x56, 0x09, 0x59, 0x86, 0x7f, 0xab, 0x8d, 0x8c, 0x7c, 0x0e,
-	0x6a, 0x3b, 0xae, 0x1f, 0x38, 0xd1, 0xe9, 0x28, 0xd4, 0x29, 0xdf, 0x96, 0xe9, 0x18, 0x62, 0xa4,
-	0xdd, 0x3f, 0x2b, 0x83, 0xdd, 0x3f, 0xb5, 0xaf, 0xc3, 0x54, 0x3a, 0x42, 0xdd, 0xad, 0xcc, 0x08,
-	0x75, 0x2f, 0x0e, 0x88, 0x50, 0xd7, 0x18, 0x1c, 0x9d, 0xee, 0x57, 0x8b, 0x30, 0xfe, 0x71, 0x89,
-	0x4c, 0x97, 0x75, 0xb5, 0xb2, 0x94, 0xf3, 0x6a, 0x65, 0xf9, 0x24, 0x57, 0x2b, 0xb5, 0x1f, 0x16,
-	0x00, 0xce, 0x2c, 0x2c, 0x9e, 0x99, 0x74, 0x83, 0xc8, 0x3d, 0x66, 0xb3, 0xbd, 0x1f, 0xfe, 0xee,
-	0x98, 0xaa, 0x12, 0xb7, 0x29, 0x7f, 0x58, 0x80, 0x49, 0x3d, 0x71, 0x8b, 0x30, 0xb7, 0x2c, 0x9e,
-	0xba, 0x94, 0x18, 0x5e, 0x82, 0x49, 0xa6, 0x63, 0x8a, 0x2d, 0x79, 0x3d, 0x7a, 0xc2, 0x2a, 0xa6,
-	0x70, 0xe8, 0x7b, 0x7b, 0x4a, 0x5c, 0x7b, 0x88, 0x63, 0x3e, 0xe5, 0xd6, 0x66, 0x69, 0x24, 0xb7,
-	0x36, 0xe3, 0xf6, 0xd5, 0xf2, 0x91, 0xf6, 0xd5, 0x3d, 0xa8, 0x6f, 0x7b, 0x6e, 0x87, 0x5f, 0x8c,
-	0x9c, 0xae, 0xf0, 0xae, 0xbc, 0x99, 0x63, 0x13, 0xee, 0x6c, 0x59, 0x0e, 0x35, 0xf9, 0xa5, 0xcb,
-	0x50, 0xf9, 0xb2, 0xac, 0xe8, 0x63, 0xc4, 0x8a, 0x1b, 0xda, 0x5c, 0xc1, 0x75, 0x6c, 0x94, 0x5c,
-	0xc3, 0x75, 0x6a, 0x43, 0x50, 0x47, 0xc5, 0x26, 0x79, 0x19, 0xb2, 0x7a, 0x46, 0x97, 0x21, 0xf7,
-	0xe3, 0x77, 0x4c, 0x6b, 0x39, 0x75, 0x90, 0x27, 0x0b, 0x64, 0xf6, 0x17, 0xaa, 0x6a, 0xed, 0x7c,
-	0xe6, 0x1e, 0x6a, 0xf9, 0x24, 0x80, 0x59, 0x9b, 0xf6, 0x45, 0x17, 0xab, 0x9d, 0x61, 0x74, 0xb1,
-	0xfa, 0x68, 0xa2, 0x8b, 0x41, 0xbe, 0xe8, 0x62, 0x8d, 0x11, 0x45, 0x17, 0x1b, 0x1f, 0x55, 0x74,
-	0xb1, 0x89, 0xa1, 0xa2, 0x8b, 0x4d, 0x1e, 0x2b, 0xba, 0xd8, 0x41, 0x09, 0x52, 0x3a, 0x86, 0x4f,
-	0x0c, 0xee, 0x7f, 0xa8, 0x0c, 0xee, 0xdf, 0x2b, 0x42, 0xb4, 0x07, 0x9c, 0xd0, 0x83, 0xfe, 0x6d,
-	0x7e, 0x89, 0x91, 0x5f, 0x88, 0x1d, 0x52, 0x34, 0x1d, 0x97, 0x17, 0x1e, 0x39, 0x0d, 0x0c, 0xa9,
-	0x11, 0x1f, 0xc0, 0x0a, 0xdf, 0x18, 0xcc, 0x6d, 0xba, 0x8c, 0x9e, 0x2b, 0x14, 0xba, 0xdf, 0xe8,
-	0x1f, 0x63, 0x6c, 0xb4, 0xdf, 0x2a, 0x81, 0x7c, 0x8c, 0x92, 0x50, 0xa8, 0x6c, 0x5b, 0x8f, 0xa9,
-	0x99, 0xdb, 0x49, 0x75, 0x99, 0x51, 0x91, 0x2f, 0x5e, 0x72, 0xdb, 0x2c, 0x4f, 0x40, 0x41, 0x9d,
-	0x1b, 0xdd, 0x84, 0xad, 0x5d, 0xb6, 0x5f, 0x0e, 0xa3, 0x5b, 0xdc, 0x66, 0x2f, 0x8d, 0x6e, 0x22,
-	0x09, 0x15, 0x0f, 0x61, 0xe3, 0xe3, 0x6e, 0x57, 0xb9, 0x5d, 0x0b, 0x12, 0xee, 0x5b, 0xca, 0xc6,
-	0xe7, 0x8b, 0xf0, 0x82, 0x92, 0x07, 0xf9, 0x16, 0x34, 0x74, 0xc3, 0xe8, 0x75, 0x7a, 0x36, 0xd7,
-	0x74, 0xe7, 0x0d, 0xc2, 0xb7, 0x10, 0xd1, 0x92, 0x6c, 0xf9, 0xc1, 0x26, 0x96, 0x8c, 0x71, 0x7e,
-	0xcd, 0x6f, 0xfc, 0xe0, 0xc7, 0xd7, 0x3e, 0xf5, 0xc3, 0x1f, 0x5f, 0xfb, 0xd4, 0x8f, 0x7e, 0x7c,
-	0xed, 0x53, 0x3f, 0x7f, 0x78, 0xad, 0xf0, 0x83, 0xc3, 0x6b, 0x85, 0x1f, 0x1e, 0x5e, 0x2b, 0xfc,
-	0xe8, 0xf0, 0x5a, 0xe1, 0x3f, 0x1c, 0x5e, 0x2b, 0xfc, 0xe5, 0xff, 0x78, 0xed, 0x53, 0x5f, 0xff,
-	0x42, 0x54, 0x9c, 0x39, 0x55, 0x9c, 0x39, 0xc5, 0x7c, 0xae, 0xbb, 0xdb, 0x9e, 0x63, 0xc5, 0x89,
-	0x52, 0x54, 0x71, 0xfe, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0x3e, 0xbb, 0x19, 0xb2, 0x01, 0xb1,
-	0x00, 0x00,
+	0x2e, 0x57, 0xb9, 0xcb, 0xae, 0x72, 0x9f, 0xb4, 0xab, 0x7a, 0xa6, 0x99, 0x69, 0xc2, 0x11, 0xd7,
+	0xe9, 0x68, 0x47, 0x46, 0x64, 0x45, 0x44, 0xba, 0xca, 0xbd, 0x8c, 0x66, 0x77, 0x46, 0xd0, 0x83,
+	0x00, 0x81, 0xf6, 0x67, 0x57, 0x42, 0x0b, 0x42, 0x42, 0xda, 0x8f, 0xd5, 0xf2, 0xb1, 0x62, 0x10,
+	0xe2, 0x03, 0x58, 0x90, 0x96, 0x01, 0x16, 0x18, 0xa1, 0x95, 0x18, 0x04, 0x58, 0x8c, 0x81, 0x0f,
+	0x90, 0x40, 0xbb, 0xac, 0x80, 0xa5, 0x40, 0x2c, 0xba, 0xaf, 0x78, 0x65, 0xa4, 0xdb, 0xce, 0x48,
+	0xbb, 0xab, 0x67, 0xfb, 0x2b, 0x33, 0xee, 0x79, 0xdd, 0xb8, 0x71, 0x1f, 0xe7, 0x9e, 0x73, 0xee,
+	0xb9, 0x70, 0xab, 0x6d, 0x05, 0x3b, 0xbd, 0xad, 0x59, 0xc3, 0xed, 0xcc, 0x39, 0xbd, 0x8e, 0xde,
+	0xf5, 0xdc, 0xf7, 0xf8, 0x9f, 0x6d, 0xdb, 0x7d, 0x34, 0xd7, 0xdd, 0x6d, 0xcf, 0xe9, 0x5d, 0xcb,
+	0x8f, 0x4a, 0xf6, 0x5e, 0xd6, 0xed, 0xee, 0x8e, 0xfe, 0xf2, 0x5c, 0x9b, 0x3a, 0xd4, 0xd3, 0x03,
+	0x6a, 0xce, 0x76, 0x3d, 0x37, 0x70, 0xc9, 0xe7, 0x23, 0x46, 0xb3, 0x8a, 0xd1, 0xac, 0x22, 0x9b,
+	0xed, 0xee, 0xb6, 0x67, 0x19, 0xa3, 0xa8, 0x44, 0x31, 0xba, 0xfa, 0xd3, 0xb1, 0x1a, 0xb4, 0xdd,
+	0xb6, 0x3b, 0xc7, 0xf9, 0x6d, 0xf5, 0xb6, 0xf9, 0x13, 0x7f, 0xe0, 0xff, 0x84, 0x9c, 0xab, 0xda,
+	0xee, 0x6b, 0xfe, 0xac, 0xe5, 0xb2, 0x6a, 0xcd, 0x19, 0xae, 0x47, 0xe7, 0xf6, 0xfa, 0xea, 0x72,
+	0xf5, 0x73, 0x11, 0x4e, 0x47, 0x37, 0x76, 0x2c, 0x87, 0x7a, 0xfb, 0xea, 0x5d, 0xe6, 0x3c, 0xea,
+	0xbb, 0x3d, 0xcf, 0xa0, 0x27, 0xa2, 0xf2, 0xe7, 0x3a, 0x34, 0xd0, 0xb3, 0x64, 0xcd, 0x0d, 0xa2,
+	0xf2, 0x7a, 0x4e, 0x60, 0x75, 0xfa, 0xc5, 0xbc, 0xfa, 0x61, 0x04, 0xbe, 0xb1, 0x43, 0x3b, 0x7a,
+	0x1f, 0xdd, 0xcf, 0x0c, 0xa2, 0xeb, 0x05, 0x96, 0x3d, 0x67, 0x39, 0x81, 0x1f, 0x78, 0x69, 0x22,
+	0xed, 0x37, 0x01, 0x2e, 0x2c, 0x6c, 0xf9, 0x81, 0xa7, 0x1b, 0xc1, 0xba, 0x6b, 0x6e, 0xd0, 0x4e,
+	0xd7, 0xd6, 0x03, 0x4a, 0x76, 0xa1, 0xc6, 0x5e, 0xc8, 0xd4, 0x03, 0x7d, 0xba, 0x70, 0xbd, 0x70,
+	0xa3, 0x31, 0xbf, 0x30, 0x3b, 0xe4, 0x07, 0x9c, 0x5d, 0x93, 0x8c, 0x9a, 0xe3, 0x87, 0x07, 0x33,
+	0x35, 0xf5, 0x84, 0xa1, 0x00, 0xf2, 0xcb, 0x05, 0x18, 0x77, 0x5c, 0x93, 0xb6, 0xa8, 0x4d, 0x8d,
+	0xc0, 0xf5, 0xa6, 0x8b, 0xd7, 0x4b, 0x37, 0x1a, 0xf3, 0xdf, 0x18, 0x5a, 0x62, 0xc6, 0x1b, 0xcd,
+	0xde, 0x8d, 0x09, 0xb8, 0xe9, 0x04, 0xde, 0x7e, 0xf3, 0xe2, 0xf7, 0x0f, 0x66, 0x9e, 0x39, 0x3c,
+	0x98, 0x19, 0x8f, 0x83, 0x30, 0x51, 0x13, 0xb2, 0x09, 0x8d, 0xc0, 0xb5, 0x59, 0x93, 0x59, 0xae,
+	0xe3, 0x4f, 0x97, 0x78, 0xc5, 0xae, 0xcd, 0x8a, 0xa6, 0x66, 0xe2, 0x67, 0x59, 0x1f, 0x9b, 0xdd,
+	0x7b, 0x79, 0x76, 0x23, 0x44, 0x6b, 0x5e, 0x90, 0x8c, 0x1b, 0x51, 0x99, 0x8f, 0x71, 0x3e, 0x84,
+	0xc2, 0x94, 0x4f, 0x8d, 0x9e, 0x67, 0x05, 0xfb, 0x8b, 0xae, 0x13, 0xd0, 0xc7, 0xc1, 0x74, 0x99,
+	0xb7, 0xf2, 0x4b, 0x59, 0xac, 0xd7, 0x5d, 0xb3, 0x95, 0xc4, 0x6e, 0x5e, 0x38, 0x3c, 0x98, 0x99,
+	0x4a, 0x15, 0x62, 0x9a, 0x27, 0x71, 0xe0, 0x9c, 0xd5, 0xd1, 0xdb, 0x74, 0xbd, 0x67, 0xdb, 0x2d,
+	0x6a, 0x78, 0x34, 0xf0, 0xa7, 0x2b, 0xfc, 0x15, 0x6e, 0x64, 0xc9, 0x59, 0x75, 0x0d, 0xdd, 0xbe,
+	0xb7, 0xf5, 0x1e, 0x35, 0x02, 0xa4, 0xdb, 0xd4, 0xa3, 0x8e, 0x41, 0x9b, 0xd3, 0xf2, 0x65, 0xce,
+	0xad, 0xa4, 0x38, 0x61, 0x1f, 0x6f, 0x72, 0x0b, 0xce, 0x77, 0x3d, 0xcb, 0xe5, 0x55, 0xb0, 0x75,
+	0xdf, 0xbf, 0xab, 0x77, 0xe8, 0xf4, 0xd8, 0xf5, 0xc2, 0x8d, 0x7a, 0xf3, 0x8a, 0x64, 0x73, 0x7e,
+	0x3d, 0x8d, 0x80, 0xfd, 0x34, 0xe4, 0x06, 0xd4, 0x54, 0xe1, 0x74, 0xf5, 0x7a, 0xe1, 0x46, 0x45,
+	0xf4, 0x1d, 0x45, 0x8b, 0x21, 0x94, 0x2c, 0x43, 0x4d, 0xdf, 0xde, 0xb6, 0x1c, 0x86, 0x59, 0xe3,
+	0x4d, 0xf8, 0x5c, 0xd6, 0xab, 0x2d, 0x48, 0x1c, 0xc1, 0x47, 0x3d, 0x61, 0x48, 0x4b, 0xde, 0x04,
+	0xe2, 0x53, 0x6f, 0xcf, 0x32, 0xe8, 0x82, 0x61, 0xb8, 0x3d, 0x27, 0xe0, 0x75, 0xaf, 0xf3, 0xba,
+	0x5f, 0x95, 0x75, 0x27, 0xad, 0x3e, 0x0c, 0xcc, 0xa0, 0x22, 0x6f, 0xc0, 0x39, 0x39, 0x56, 0xa3,
+	0x56, 0x00, 0xce, 0xe9, 0x22, 0x6b, 0x48, 0x4c, 0xc1, 0xb0, 0x0f, 0x9b, 0x98, 0xf0, 0x9c, 0xde,
+	0x0b, 0xdc, 0x0e, 0x63, 0x99, 0x14, 0xba, 0xe1, 0xee, 0x52, 0x67, 0xba, 0x71, 0xbd, 0x70, 0xa3,
+	0xd6, 0xbc, 0x7e, 0x78, 0x30, 0xf3, 0xdc, 0xc2, 0x11, 0x78, 0x78, 0x24, 0x17, 0x72, 0x0f, 0xea,
+	0xa6, 0xe3, 0xaf, 0xbb, 0xb6, 0x65, 0xec, 0x4f, 0x8f, 0xf3, 0x0a, 0xbe, 0x2c, 0x5f, 0xb5, 0xbe,
+	0x74, 0xb7, 0x25, 0x00, 0x4f, 0x0e, 0x66, 0x9e, 0xeb, 0x9f, 0x52, 0x67, 0x43, 0x38, 0x46, 0x3c,
+	0xc8, 0x1a, 0x67, 0xb8, 0xe8, 0x3a, 0xdb, 0x56, 0x7b, 0x7a, 0x82, 0x7f, 0x8d, 0xeb, 0x03, 0x3a,
+	0xf4, 0xd2, 0xdd, 0x96, 0xc0, 0x6b, 0x4e, 0x48, 0x71, 0xe2, 0x11, 0x23, 0x0e, 0xc4, 0x84, 0x49,
+	0x35, 0x19, 0x2f, 0xda, 0xba, 0xd5, 0xf1, 0xa7, 0x27, 0x79, 0xe7, 0xfd, 0x89, 0x01, 0x3c, 0x31,
+	0x8e, 0xdc, 0xbc, 0x2c, 0x5f, 0x65, 0x32, 0x51, 0xec, 0x63, 0x8a, 0xe7, 0xd5, 0xd7, 0xe1, 0x7c,
+	0xdf, 0xdc, 0x40, 0xce, 0x41, 0x69, 0x97, 0xee, 0xf3, 0xa9, 0xaf, 0x8e, 0xec, 0x2f, 0xb9, 0x08,
+	0x95, 0x3d, 0xdd, 0xee, 0xd1, 0xe9, 0x22, 0x2f, 0x13, 0x0f, 0x5f, 0x2c, 0xbe, 0x56, 0xd0, 0xfe,
+	0x63, 0x19, 0xc6, 0xd5, 0x8c, 0xd3, 0xb2, 0x9c, 0x5d, 0xf2, 0x00, 0x4a, 0xb6, 0xdb, 0x96, 0xf3,
+	0xe6, 0xcf, 0x0e, 0x3d, 0x8b, 0xad, 0xba, 0xed, 0x66, 0xf5, 0xf0, 0x60, 0xa6, 0xb4, 0xea, 0xb6,
+	0x91, 0x71, 0x24, 0x06, 0x54, 0x76, 0xf5, 0xed, 0x5d, 0x9d, 0xd7, 0xa1, 0x31, 0xdf, 0x1c, 0x9a,
+	0xf5, 0x1d, 0xc6, 0x85, 0xd5, 0xb5, 0x59, 0x3f, 0x3c, 0x98, 0xa9, 0xf0, 0x47, 0x14, 0xbc, 0x89,
+	0x0b, 0xf5, 0x2d, 0x5b, 0x37, 0x76, 0x77, 0x5c, 0x9b, 0x4e, 0x97, 0x72, 0x0a, 0x6a, 0x2a, 0x4e,
+	0xe2, 0x33, 0x87, 0x8f, 0x18, 0xc9, 0x20, 0x06, 0x8c, 0xf5, 0x4c, 0xdf, 0x72, 0x76, 0xe5, 0x1c,
+	0xf8, 0xfa, 0xd0, 0xd2, 0x36, 0x97, 0xf8, 0x3b, 0xc1, 0xe1, 0xc1, 0xcc, 0x98, 0xf8, 0x8f, 0x92,
+	0x35, 0x6b, 0x3a, 0x36, 0x52, 0xe9, 0x74, 0x25, 0xe7, 0x1b, 0xb1, 0x81, 0x44, 0xa3, 0xa6, 0xe3,
+	0x8f, 0x28, 0x78, 0x93, 0x77, 0xa0, 0xe4, 0x3f, 0xf4, 0xf9, 0x8c, 0xd7, 0x98, 0x7f, 0x63, 0x78,
+	0x11, 0x0f, 0x7d, 0x2e, 0x80, 0x7f, 0xfc, 0xd6, 0x43, 0x1f, 0x19, 0x57, 0xed, 0x0f, 0xc6, 0x61,
+	0x52, 0x75, 0xb3, 0xfb, 0xd4, 0x0b, 0xe8, 0x63, 0x72, 0x1d, 0xca, 0x0e, 0x9b, 0x5c, 0x78, 0x37,
+	0x6d, 0x8e, 0xcb, 0x0e, 0x5f, 0xe6, 0x93, 0x0a, 0x87, 0xb0, 0xb6, 0x15, 0x9d, 0x5d, 0x76, 0x99,
+	0xe1, 0xdb, 0xb6, 0xc5, 0xd9, 0x88, 0xb6, 0x15, 0xff, 0x51, 0xb2, 0x26, 0xef, 0x40, 0x99, 0x7f,
+	0x3e, 0xd1, 0x59, 0xbe, 0x3c, 0xbc, 0x08, 0xf6, 0xd2, 0x35, 0xf6, 0x06, 0xfc, 0xd3, 0x71, 0xa6,
+	0x6c, 0x30, 0xf5, 0xcc, 0x6d, 0xd9, 0x35, 0x7e, 0x36, 0x47, 0xd7, 0x58, 0x16, 0xed, 0xb9, 0xb9,
+	0xb4, 0x8c, 0x8c, 0x23, 0xf9, 0x8b, 0x05, 0x38, 0x6f, 0xb8, 0x4e, 0xa0, 0x33, 0x4d, 0x49, 0xa9,
+	0x09, 0xb2, 0x7b, 0xbc, 0x39, 0xb4, 0x9c, 0xc5, 0x34, 0xc7, 0xe6, 0x25, 0xb6, 0xea, 0xf5, 0x15,
+	0x63, 0xbf, 0x6c, 0xf2, 0x97, 0x0b, 0x70, 0x89, 0xad, 0x46, 0x7d, 0xc8, 0xb2, 0x47, 0x8d, 0xb2,
+	0x56, 0x57, 0x0e, 0x0f, 0x66, 0x2e, 0xad, 0x64, 0x09, 0xc3, 0xec, 0x3a, 0xb0, 0xda, 0x5d, 0xd0,
+	0xfb, 0x15, 0x2b, 0xbe, 0x3e, 0x37, 0xe6, 0x57, 0x47, 0xa9, 0xac, 0x35, 0x3f, 0x25, 0xbb, 0x72,
+	0x96, 0x6e, 0x8a, 0x59, 0xb5, 0x20, 0x37, 0xa1, 0xba, 0xe7, 0xda, 0xbd, 0x0e, 0xf5, 0xa7, 0x6b,
+	0x7c, 0x91, 0xb8, 0x9a, 0xb5, 0x48, 0xdc, 0xe7, 0x28, 0xcd, 0x29, 0xc9, 0xbe, 0x2a, 0x9e, 0x7d,
+	0x54, 0xb4, 0xc4, 0x82, 0x31, 0xdb, 0xea, 0x58, 0x81, 0xcf, 0x97, 0xfe, 0xc6, 0xfc, 0xcd, 0xa1,
+	0x5f, 0x4b, 0x0c, 0xd1, 0x55, 0xce, 0x4c, 0x8c, 0x1a, 0xf1, 0x1f, 0xa5, 0x00, 0x3e, 0x23, 0x19,
+	0xba, 0x2d, 0x54, 0x83, 0xc6, 0xfc, 0x57, 0x86, 0x1f, 0x36, 0x8c, 0x4b, 0x73, 0x42, 0xbe, 0x53,
+	0x85, 0x3f, 0xa2, 0xe0, 0x4d, 0xbe, 0x0e, 0x93, 0x89, 0xaf, 0xe9, 0x4f, 0x37, 0x78, 0xeb, 0x3c,
+	0x9f, 0xd5, 0x3a, 0x21, 0x56, 0xb4, 0x76, 0x26, 0x7a, 0x88, 0x8f, 0x29, 0x66, 0xe4, 0x0e, 0xd4,
+	0x7c, 0xcb, 0xa4, 0x86, 0xee, 0xf9, 0xd3, 0xe3, 0xc7, 0x61, 0x7c, 0x4e, 0x32, 0xae, 0xb5, 0x24,
+	0x19, 0x86, 0x0c, 0xc8, 0x2c, 0x40, 0x57, 0xf7, 0x02, 0x4b, 0xa8, 0xda, 0x13, 0x5c, 0xed, 0x9b,
+	0x3c, 0x3c, 0x98, 0x81, 0xf5, 0xb0, 0x14, 0x63, 0x18, 0x0c, 0x9f, 0xd1, 0xae, 0x38, 0xdd, 0x5e,
+	0x20, 0x54, 0x83, 0xba, 0xc0, 0x6f, 0x85, 0xa5, 0x18, 0xc3, 0x20, 0xbf, 0x5e, 0x80, 0x4f, 0x45,
+	0x8f, 0xfd, 0x83, 0x6c, 0x6a, 0xe4, 0x83, 0x6c, 0xe6, 0xf0, 0x60, 0xe6, 0x53, 0xad, 0xc1, 0x22,
+	0xf1, 0xa8, 0xfa, 0x90, 0x0f, 0x0a, 0x30, 0xd9, 0xeb, 0x9a, 0x7a, 0x40, 0x5b, 0x01, 0xdb, 0xb3,
+	0xb5, 0xf7, 0xa7, 0xcf, 0xf1, 0x2a, 0xde, 0x1a, 0x7e, 0x16, 0x4c, 0xb0, 0x8b, 0x3e, 0x73, 0xb2,
+	0x1c, 0x53, 0x62, 0xb5, 0xf7, 0xe0, 0xfc, 0x82, 0x61, 0xf4, 0x3a, 0x3d, 0x5b, 0x0f, 0x5c, 0xef,
+	0x81, 0xe5, 0x98, 0xee, 0x23, 0xb2, 0x09, 0x55, 0xa6, 0xb4, 0xba, 0xbd, 0x40, 0x6a, 0x3a, 0xb3,
+	0xb1, 0x4f, 0x1f, 0xee, 0x40, 0xa3, 0xda, 0xb0, 0xed, 0x1e, 0xeb, 0x0c, 0x4b, 0x3d, 0xb9, 0x4d,
+	0x6a, 0xb0, 0x11, 0xb8, 0x21, 0x58, 0xa0, 0xe2, 0xa5, 0x3d, 0x80, 0x89, 0x85, 0x5e, 0xb0, 0xe3,
+	0x7a, 0xd6, 0xfb, 0x1c, 0x8d, 0x2c, 0x43, 0x25, 0xe0, 0x4a, 0xaf, 0x90, 0xf2, 0x62, 0x56, 0x07,
+	0x13, 0x1b, 0x90, 0x3b, 0x74, 0x5f, 0x69, 0x71, 0x62, 0x71, 0x16, 0x4a, 0xb0, 0x20, 0xd7, 0x7e,
+	0xa9, 0x08, 0xd5, 0xa6, 0x6e, 0xec, 0xba, 0xdb, 0xdb, 0xe4, 0x6d, 0xa8, 0x59, 0x4e, 0x40, 0xbd,
+	0x3d, 0xdd, 0x1e, 0xb2, 0xf2, 0x7c, 0x1f, 0xb1, 0x22, 0x79, 0x60, 0xc8, 0x8d, 0xcc, 0x40, 0xc5,
+	0x0f, 0x68, 0xd7, 0xe7, 0xeb, 0xed, 0x84, 0xd4, 0x11, 0x58, 0x01, 0x8a, 0x72, 0xa2, 0xc1, 0xd8,
+	0xb6, 0xce, 0x77, 0xb9, 0x6c, 0xb9, 0x2c, 0x88, 0xa9, 0x61, 0x99, 0x97, 0xa0, 0x84, 0x90, 0x15,
+	0x28, 0x19, 0x7a, 0x57, 0xae, 0x79, 0x27, 0xad, 0x19, 0x5f, 0xe5, 0x16, 0xf5, 0x2e, 0x32, 0x1e,
+	0x4c, 0xdc, 0x7b, 0x56, 0x10, 0x50, 0x8f, 0xaf, 0x6c, 0x52, 0xdc, 0x9b, 0xbc, 0x04, 0x25, 0x44,
+	0xfb, 0x6b, 0x05, 0xa8, 0x37, 0x75, 0xdf, 0x32, 0x58, 0xc3, 0x93, 0x45, 0x28, 0xf7, 0x7c, 0xea,
+	0x9d, 0xac, 0xb9, 0xf9, 0xaa, 0xbd, 0xe9, 0x53, 0x0f, 0x39, 0x31, 0xb9, 0x07, 0xb5, 0xae, 0xee,
+	0xfb, 0x8f, 0x5c, 0xcf, 0x94, 0x9a, 0xc7, 0x31, 0x19, 0x89, 0x7d, 0x9e, 0x24, 0xc5, 0x90, 0x89,
+	0xd6, 0x80, 0x48, 0x79, 0xd4, 0x7e, 0xbf, 0x00, 0x17, 0x9a, 0xbd, 0xed, 0x6d, 0xea, 0xc9, 0x6d,
+	0x8d, 0xdc, 0x30, 0x50, 0xa8, 0x78, 0xd4, 0xb4, 0x7c, 0x59, 0xf7, 0xa5, 0xa1, 0xc7, 0x09, 0x32,
+	0x2e, 0x72, 0x7f, 0xc2, 0x3f, 0x21, 0x2f, 0x40, 0xc1, 0x9d, 0xf4, 0xa0, 0xfe, 0x1e, 0x0d, 0xfc,
+	0xc0, 0xa3, 0x7a, 0x47, 0xbe, 0xdd, 0xed, 0xa1, 0x45, 0xbd, 0x49, 0x83, 0x16, 0xe7, 0x14, 0xdf,
+	0x0e, 0x85, 0x85, 0x18, 0x49, 0xd2, 0x7e, 0xb3, 0x02, 0xe3, 0x8b, 0x6e, 0x67, 0xcb, 0x72, 0xa8,
+	0x79, 0xd3, 0x6c, 0x53, 0xf2, 0x2e, 0x94, 0xa9, 0xd9, 0xa6, 0xf2, 0x6d, 0x87, 0xd7, 0xbb, 0x18,
+	0xb3, 0x48, 0x7b, 0x64, 0x4f, 0xc8, 0x19, 0x93, 0x55, 0x98, 0xdc, 0xf6, 0xdc, 0x8e, 0x58, 0xca,
+	0x36, 0xf6, 0xbb, 0x72, 0xf3, 0xd3, 0xfc, 0x09, 0x35, 0x6f, 0x2c, 0x27, 0xa0, 0x4f, 0x0e, 0x66,
+	0x20, 0x7a, 0xc2, 0x14, 0x2d, 0x79, 0x1b, 0xa6, 0xa3, 0x92, 0x70, 0x4e, 0x5f, 0x64, 0xfb, 0x51,
+	0x3e, 0x16, 0x2a, 0xcd, 0xe7, 0x0e, 0x0f, 0x66, 0xa6, 0x97, 0x07, 0xe0, 0xe0, 0x40, 0x6a, 0x36,
+	0x53, 0x9e, 0x8b, 0x80, 0x62, 0x9d, 0x95, 0xa3, 0x67, 0x44, 0x0b, 0x38, 0xdf, 0xb8, 0x2f, 0xa7,
+	0x44, 0x60, 0x9f, 0x50, 0xb2, 0x0c, 0xe3, 0x81, 0x1b, 0x6b, 0xaf, 0x0a, 0x6f, 0x2f, 0x4d, 0x59,
+	0x9a, 0x36, 0xdc, 0x81, 0xad, 0x95, 0xa0, 0x23, 0x08, 0x97, 0xd5, 0x73, 0xaa, 0xa5, 0xc6, 0x78,
+	0x4b, 0x5d, 0x3d, 0x3c, 0x98, 0xb9, 0xbc, 0x91, 0x89, 0x81, 0x03, 0x28, 0xc9, 0x2f, 0x14, 0x60,
+	0x52, 0x81, 0x64, 0x1b, 0x55, 0x47, 0xd9, 0x46, 0x84, 0xf5, 0x88, 0x8d, 0x84, 0x00, 0x4c, 0x09,
+	0xd4, 0xbe, 0x57, 0x85, 0x7a, 0xb8, 0xd2, 0x91, 0x17, 0xa0, 0xc2, 0x6d, 0x48, 0x72, 0x03, 0x13,
+	0xaa, 0x30, 0xdc, 0xd4, 0x84, 0x02, 0x46, 0x5e, 0x84, 0xaa, 0xe1, 0x76, 0x3a, 0xba, 0x63, 0x72,
+	0xbb, 0x60, 0x5d, 0xac, 0x1b, 0x8b, 0xa2, 0x08, 0x15, 0x8c, 0x3c, 0x07, 0x65, 0xdd, 0x6b, 0x0b,
+	0x13, 0x5d, 0x5d, 0xcc, 0x47, 0x0b, 0x5e, 0xdb, 0x47, 0x5e, 0x4a, 0xbe, 0x00, 0x25, 0xea, 0xec,
+	0x4d, 0x97, 0x07, 0xab, 0x86, 0x37, 0x9d, 0xbd, 0xfb, 0xba, 0xd7, 0x6c, 0xc8, 0x3a, 0x94, 0x6e,
+	0x3a, 0x7b, 0xc8, 0x68, 0xc8, 0x2a, 0x54, 0xa9, 0xb3, 0xc7, 0xbe, 0xbd, 0xb4, 0x9d, 0x7d, 0x7a,
+	0x00, 0x39, 0x43, 0x91, 0xbb, 0xa4, 0x50, 0xc1, 0x94, 0xc5, 0xa8, 0x58, 0x90, 0xaf, 0xc2, 0xb8,
+	0xd0, 0x35, 0xd7, 0xd8, 0x37, 0x61, 0x7b, 0x45, 0xc6, 0x72, 0x66, 0xb0, 0xb2, 0xca, 0xf1, 0x22,
+	0x5b, 0x65, 0xac, 0xd0, 0xc7, 0x04, 0x2b, 0xf2, 0x55, 0xa8, 0x2b, 0xd3, 0x86, 0xfa, 0xb2, 0x99,
+	0x66, 0x3e, 0x65, 0x0f, 0x41, 0xfa, 0xb0, 0x67, 0x79, 0xb4, 0x43, 0x9d, 0xc0, 0x6f, 0x9e, 0x57,
+	0x86, 0x1f, 0x05, 0xf5, 0x31, 0xe2, 0x46, 0xb6, 0xfa, 0xed, 0x95, 0xc2, 0xd8, 0xf6, 0xc2, 0x80,
+	0x59, 0x7d, 0x08, 0x63, 0xe5, 0x37, 0x60, 0x2a, 0x34, 0x28, 0x4a, 0x9b, 0x94, 0x30, 0xbf, 0x7d,
+	0x8e, 0x91, 0xaf, 0x24, 0x41, 0x4f, 0x0e, 0x66, 0x9e, 0xcf, 0xb0, 0x4a, 0x45, 0x08, 0x98, 0x66,
+	0x46, 0xde, 0x87, 0x49, 0x8f, 0xea, 0xa6, 0xe5, 0x50, 0xdf, 0x5f, 0xf7, 0xdc, 0xad, 0xfc, 0x8a,
+	0x37, 0xe7, 0x22, 0xba, 0x3d, 0x26, 0x38, 0x63, 0x4a, 0x12, 0x79, 0x04, 0x13, 0xb6, 0xb5, 0x47,
+	0x23, 0xd1, 0x8d, 0x91, 0x88, 0x3e, 0x7f, 0x78, 0x30, 0x33, 0xb1, 0x1a, 0x67, 0x8c, 0x49, 0x39,
+	0x4c, 0x79, 0xea, 0xba, 0x5e, 0xa0, 0xb4, 0xf3, 0x4f, 0x1f, 0xa9, 0x9d, 0xaf, 0xbb, 0x5e, 0x10,
+	0x0d, 0x42, 0xf6, 0xe4, 0xa3, 0x20, 0xd7, 0xfe, 0x66, 0x05, 0xfa, 0xf7, 0xb0, 0xc9, 0x1e, 0x57,
+	0x18, 0x75, 0x8f, 0x4b, 0xf7, 0x06, 0xb1, 0xf6, 0xbc, 0x26, 0xc9, 0x46, 0xd0, 0x23, 0x32, 0x7a,
+	0x75, 0x69, 0xd4, 0xbd, 0xfa, 0xa9, 0x99, 0x78, 0xfa, 0xbb, 0xff, 0xd8, 0x47, 0xd7, 0xfd, 0xab,
+	0x67, 0xd3, 0xfd, 0xb5, 0xef, 0x96, 0x61, 0x72, 0x49, 0xa7, 0x1d, 0xd7, 0xf9, 0x50, 0x33, 0x46,
+	0xe1, 0xa9, 0x30, 0x63, 0xdc, 0x80, 0x9a, 0x47, 0xbb, 0xb6, 0x65, 0xe8, 0x62, 0x07, 0x21, 0x1d,
+	0x1f, 0x28, 0xcb, 0x30, 0x84, 0x0e, 0x30, 0x5f, 0x95, 0x9e, 0x4a, 0xf3, 0x55, 0xf9, 0xa3, 0x37,
+	0x5f, 0x69, 0xbf, 0x50, 0x04, 0xae, 0xda, 0x92, 0xeb, 0x50, 0x66, 0x6a, 0x5b, 0xda, 0x68, 0xca,
+	0x47, 0x0b, 0x87, 0x90, 0xab, 0x50, 0x0c, 0x5c, 0x39, 0xdd, 0x80, 0x84, 0x17, 0x37, 0x5c, 0x2c,
+	0x06, 0x2e, 0x79, 0x1f, 0xc0, 0x70, 0x1d, 0xd3, 0x52, 0xfe, 0xc0, 0x7c, 0x2f, 0xb6, 0xec, 0x7a,
+	0x8f, 0x74, 0xcf, 0x5c, 0x0c, 0x39, 0x0a, 0x03, 0x46, 0xf4, 0x8c, 0x31, 0x69, 0xe4, 0x75, 0x18,
+	0x73, 0x9d, 0xe5, 0x9e, 0x6d, 0xf3, 0x06, 0xad, 0x37, 0x3f, 0xc3, 0xf6, 0x72, 0xf7, 0x78, 0xc9,
+	0x93, 0x83, 0x99, 0x2b, 0x62, 0x47, 0xc4, 0x9e, 0x1e, 0x78, 0x56, 0x60, 0x39, 0xed, 0x70, 0x3f,
+	0x2f, 0xc9, 0xb4, 0x5f, 0x2c, 0x40, 0x63, 0xd9, 0x7a, 0x4c, 0x4d, 0xb9, 0x85, 0x47, 0x18, 0xb3,
+	0xa9, 0xd3, 0x0e, 0x76, 0x86, 0xdc, 0x04, 0x0b, 0xb3, 0x16, 0xe7, 0x80, 0x92, 0x13, 0x99, 0x83,
+	0xba, 0xd8, 0xaf, 0x58, 0x4e, 0x9b, 0xb7, 0x61, 0x2d, 0x9a, 0xe9, 0x5b, 0x0a, 0x80, 0x11, 0x8e,
+	0xb6, 0x0f, 0xe7, 0xfb, 0x9a, 0x81, 0x98, 0x50, 0x0e, 0xf4, 0xb6, 0x5a, 0x54, 0x96, 0x87, 0x6e,
+	0xe0, 0x0d, 0xbd, 0x1d, 0x6b, 0x5c, 0xae, 0x15, 0x6e, 0xe8, 0x4c, 0x2b, 0x64, 0xdc, 0xb5, 0xff,
+	0x5b, 0x80, 0xda, 0x72, 0xcf, 0x31, 0xb8, 0x9d, 0xe1, 0xc3, 0x8d, 0xe9, 0x4a, 0xc5, 0x2c, 0x66,
+	0xaa, 0x98, 0x3d, 0x18, 0xdb, 0x7d, 0x14, 0xaa, 0xa0, 0x8d, 0xf9, 0xb5, 0xe1, 0x7b, 0x85, 0xac,
+	0xd2, 0xec, 0x1d, 0xce, 0x4f, 0x78, 0xab, 0x27, 0x65, 0x85, 0xc6, 0xee, 0x3c, 0xe0, 0x42, 0xa5,
+	0xb0, 0xab, 0x5f, 0x80, 0x46, 0x0c, 0xed, 0x44, 0x8e, 0xab, 0xbf, 0x55, 0x86, 0xb1, 0x5b, 0xad,
+	0xd6, 0xc2, 0xfa, 0x0a, 0x79, 0x05, 0x1a, 0xd2, 0x91, 0x79, 0x37, 0x6a, 0x83, 0xd0, 0x8f, 0xdd,
+	0x8a, 0x40, 0x18, 0xc7, 0x63, 0x0a, 0xbc, 0x47, 0x75, 0xbb, 0x23, 0x07, 0x4b, 0xa8, 0x3b, 0x20,
+	0x2b, 0x44, 0x01, 0x23, 0x3a, 0x4c, 0xf6, 0x7c, 0xea, 0xb1, 0x26, 0x14, 0xfb, 0x7d, 0x39, 0x6c,
+	0x8e, 0x69, 0x11, 0xe0, 0x0b, 0xcc, 0x66, 0x82, 0x01, 0xa6, 0x18, 0x92, 0xd7, 0xa0, 0xa6, 0xf7,
+	0x82, 0x1d, 0xbe, 0xe5, 0x12, 0x63, 0xe3, 0x39, 0xee, 0xe7, 0x95, 0x65, 0x4f, 0x0e, 0x66, 0xc6,
+	0xef, 0x60, 0xf3, 0x15, 0xf5, 0x8c, 0x21, 0x36, 0xab, 0x9c, 0xb2, 0x31, 0xc8, 0xca, 0x55, 0x4e,
+	0x5c, 0xb9, 0xf5, 0x04, 0x03, 0x4c, 0x31, 0x24, 0xef, 0xc0, 0xf8, 0x2e, 0xdd, 0x0f, 0xf4, 0x2d,
+	0x29, 0x60, 0xec, 0x24, 0x02, 0xce, 0x31, 0xa5, 0xff, 0x4e, 0x8c, 0x1c, 0x13, 0xcc, 0x88, 0x0f,
+	0x17, 0x77, 0xa9, 0xb7, 0x45, 0x3d, 0x57, 0xda, 0x2b, 0xa4, 0x90, 0xea, 0x49, 0x84, 0x4c, 0x1f,
+	0x1e, 0xcc, 0x5c, 0xbc, 0x93, 0xc1, 0x06, 0x33, 0x99, 0x6b, 0xff, 0xbb, 0x08, 0x53, 0xb7, 0x44,
+	0x24, 0x89, 0xeb, 0x09, 0xcd, 0x83, 0x5c, 0x81, 0x92, 0xd7, 0xed, 0xf1, 0x9e, 0x53, 0x12, 0x36,
+	0x28, 0x5c, 0xdf, 0x44, 0x56, 0x46, 0xde, 0x86, 0x9a, 0x29, 0xa7, 0x0c, 0x69, 0x2e, 0x19, 0xca,
+	0xda, 0xa6, 0x9e, 0x30, 0xe4, 0xc6, 0xf6, 0x86, 0x1d, 0xbf, 0xdd, 0xb2, 0xde, 0xa7, 0xd2, 0x82,
+	0xc0, 0xf7, 0x86, 0x6b, 0xa2, 0x08, 0x15, 0x8c, 0xad, 0xaa, 0xbb, 0x74, 0x5f, 0xec, 0x9f, 0xcb,
+	0xd1, 0xaa, 0x7a, 0x47, 0x96, 0x61, 0x08, 0x25, 0x33, 0x6a, 0xb0, 0xb0, 0x5e, 0x50, 0x16, 0xb6,
+	0x9f, 0xfb, 0xac, 0x40, 0x8e, 0x1b, 0x36, 0x65, 0x4a, 0x7b, 0xda, 0xd8, 0xf0, 0x53, 0x66, 0xd2,
+	0xfe, 0x46, 0x7e, 0x0a, 0xea, 0x9c, 0x79, 0xd3, 0x76, 0xb7, 0xf8, 0x87, 0xab, 0x0b, 0x2b, 0xd0,
+	0x7d, 0x55, 0x88, 0x11, 0x5c, 0xfb, 0xc3, 0x22, 0x5c, 0xbe, 0x45, 0x03, 0xa1, 0xd5, 0x2c, 0xd1,
+	0xae, 0xed, 0xee, 0x33, 0x7d, 0x1a, 0xe9, 0x43, 0xf2, 0x06, 0x80, 0xe5, 0x6f, 0xb5, 0xf6, 0x0c,
+	0x3e, 0x0e, 0xc4, 0x18, 0xbe, 0x2e, 0x87, 0x24, 0xac, 0xb4, 0x9a, 0x12, 0xf2, 0x24, 0xf1, 0x84,
+	0x31, 0x9a, 0x68, 0x43, 0x5e, 0x3c, 0x62, 0x43, 0xde, 0x02, 0xe8, 0x46, 0x5a, 0x79, 0x89, 0x63,
+	0xfe, 0x8c, 0x12, 0x73, 0x12, 0x85, 0x3c, 0xc6, 0x26, 0x8f, 0x9e, 0xec, 0xc0, 0x39, 0x93, 0x6e,
+	0xeb, 0x3d, 0x3b, 0x08, 0x77, 0x12, 0x72, 0x10, 0x1f, 0x7f, 0x33, 0x12, 0x46, 0xb9, 0x2c, 0xa5,
+	0x38, 0x61, 0x1f, 0x6f, 0xed, 0xef, 0x94, 0xe0, 0xea, 0x2d, 0x1a, 0x84, 0x36, 0x3a, 0x39, 0x3b,
+	0xb6, 0xba, 0xd4, 0x60, 0x5f, 0xe1, 0x83, 0x02, 0x8c, 0xd9, 0xfa, 0x16, 0xb5, 0xd9, 0xea, 0xc5,
+	0xde, 0xe6, 0xdd, 0xa1, 0x17, 0x82, 0xc1, 0x52, 0x66, 0x57, 0xb9, 0x84, 0xd4, 0xd2, 0x20, 0x0a,
+	0x51, 0x8a, 0x67, 0x93, 0xba, 0x61, 0xf7, 0xfc, 0x40, 0xec, 0xec, 0xa4, 0x3e, 0x19, 0x4e, 0xea,
+	0x8b, 0x11, 0x08, 0xe3, 0x78, 0x64, 0x1e, 0xc0, 0xb0, 0x2d, 0xea, 0x04, 0x9c, 0x4a, 0x8c, 0x2b,
+	0xa2, 0xbe, 0xef, 0x62, 0x08, 0xc1, 0x18, 0x16, 0x13, 0xd5, 0x71, 0x1d, 0x2b, 0x70, 0x85, 0xa8,
+	0x72, 0x52, 0xd4, 0x5a, 0x04, 0xc2, 0x38, 0x1e, 0x27, 0xa3, 0x81, 0x67, 0x19, 0x3e, 0x27, 0xab,
+	0xa4, 0xc8, 0x22, 0x10, 0xc6, 0xf1, 0xd8, 0x9a, 0x17, 0x7b, 0xff, 0x13, 0xad, 0x79, 0xbf, 0x56,
+	0x87, 0x6b, 0x89, 0x66, 0x0d, 0xf4, 0x80, 0x6e, 0xf7, 0xec, 0x16, 0x0d, 0xd4, 0x07, 0x1c, 0x72,
+	0x2d, 0xfc, 0x73, 0xd1, 0x77, 0x17, 0xf1, 0x6b, 0xc6, 0x68, 0xbe, 0x7b, 0x5f, 0x05, 0x8f, 0xf5,
+	0xed, 0xe7, 0xa0, 0xee, 0xe8, 0x81, 0xcf, 0x07, 0xae, 0x1c, 0xa3, 0xa1, 0x1a, 0x76, 0x57, 0x01,
+	0x30, 0xc2, 0x21, 0xeb, 0x70, 0x51, 0x36, 0xf1, 0xcd, 0xc7, 0x6c, 0xcf, 0x4f, 0x3d, 0x41, 0x2b,
+	0x97, 0x53, 0x49, 0x7b, 0x71, 0x2d, 0x03, 0x07, 0x33, 0x29, 0xc9, 0x1a, 0x5c, 0x30, 0x44, 0x4c,
+	0x0f, 0xb5, 0x5d, 0xdd, 0x54, 0x0c, 0x85, 0x49, 0x34, 0xdc, 0x1a, 0x2d, 0xf6, 0xa3, 0x60, 0x16,
+	0x5d, 0xba, 0x37, 0x8f, 0x0d, 0xd5, 0x9b, 0xab, 0xc3, 0xf4, 0xe6, 0xda, 0x70, 0xbd, 0xb9, 0x7e,
+	0xbc, 0xde, 0xcc, 0x5a, 0x9e, 0x87, 0x8f, 0x78, 0x4c, 0x3d, 0x11, 0x2b, 0x6c, 0x2c, 0x64, 0x2c,
+	0x6c, 0xf9, 0x56, 0x06, 0x0e, 0x66, 0x52, 0x92, 0x2d, 0xb8, 0x2a, 0xca, 0x6f, 0x3a, 0x86, 0xb7,
+	0xdf, 0x65, 0x0b, 0x4f, 0x8c, 0x6f, 0x23, 0x61, 0x93, 0xbe, 0xda, 0x1a, 0x88, 0x89, 0x47, 0x70,
+	0x21, 0x5f, 0x82, 0x09, 0xf1, 0x95, 0xd6, 0xf4, 0x2e, 0x67, 0x2b, 0x02, 0xc8, 0x2e, 0x49, 0xb6,
+	0x13, 0x8b, 0x71, 0x20, 0x26, 0x71, 0xc9, 0x02, 0x4c, 0x75, 0xf7, 0x0c, 0xf6, 0x77, 0x65, 0xfb,
+	0x2e, 0xa5, 0x26, 0x35, 0xb9, 0xbf, 0xb7, 0xde, 0x7c, 0x56, 0x59, 0x77, 0xd6, 0x93, 0x60, 0x4c,
+	0xe3, 0x93, 0xd7, 0x60, 0xdc, 0x0f, 0x74, 0x2f, 0x90, 0x86, 0xe0, 0xe9, 0x49, 0x11, 0x60, 0xa7,
+	0xec, 0xa4, 0xad, 0x18, 0x0c, 0x13, 0x98, 0x99, 0xeb, 0xc5, 0xd4, 0xe9, 0xad, 0x17, 0x79, 0x66,
+	0xab, 0x7f, 0x54, 0x84, 0xeb, 0xb7, 0x68, 0xb0, 0xe6, 0x3a, 0xd2, 0x8c, 0x9e, 0xb5, 0xec, 0x1f,
+	0xcb, 0x8a, 0x9e, 0x5c, 0xb4, 0x8b, 0x23, 0x5d, 0xb4, 0x4b, 0x23, 0x5a, 0xb4, 0xcb, 0xa7, 0xb8,
+	0x68, 0xff, 0xdd, 0x22, 0x3c, 0x9b, 0x68, 0xc9, 0x75, 0xd7, 0x54, 0x13, 0xfe, 0x27, 0x0d, 0x78,
+	0x8c, 0x06, 0x7c, 0x22, 0xf4, 0x4e, 0xee, 0x08, 0x4d, 0x69, 0x3c, 0xdf, 0x49, 0x6b, 0x3c, 0xef,
+	0xe4, 0x59, 0xf9, 0x32, 0x24, 0x1c, 0x6b, 0xc5, 0x7b, 0x13, 0x88, 0x27, 0xdd, 0xb6, 0x91, 0x39,
+	0x5b, 0x2a, 0x3d, 0x61, 0x04, 0x2f, 0xf6, 0x61, 0x60, 0x06, 0x15, 0x69, 0xc1, 0x25, 0x9f, 0x3a,
+	0x81, 0xe5, 0x50, 0x3b, 0xc9, 0x4e, 0x68, 0x43, 0xcf, 0x4b, 0x76, 0x97, 0x5a, 0x59, 0x48, 0x98,
+	0x4d, 0x9b, 0x67, 0x1e, 0xf8, 0x6d, 0xe0, 0x2a, 0xa7, 0x68, 0x9a, 0x91, 0x69, 0x2c, 0x1f, 0xa4,
+	0x35, 0x96, 0x77, 0xf3, 0x7f, 0xb7, 0xe1, 0xb4, 0x95, 0x79, 0x00, 0xfe, 0x15, 0xe2, 0xea, 0x4a,
+	0xb8, 0x48, 0x63, 0x08, 0xc1, 0x18, 0x16, 0x5b, 0x80, 0x54, 0x3b, 0xc7, 0x35, 0x95, 0x70, 0x01,
+	0x6a, 0xc5, 0x81, 0x98, 0xc4, 0x1d, 0xa8, 0xed, 0x54, 0x86, 0xd6, 0x76, 0xde, 0x04, 0x92, 0x30,
+	0x3c, 0x0a, 0x7e, 0x63, 0xc9, 0x00, 0xf2, 0x95, 0x3e, 0x0c, 0xcc, 0xa0, 0x1a, 0xd0, 0x95, 0xab,
+	0xa3, 0xed, 0xca, 0xb5, 0xe1, 0xbb, 0x32, 0x79, 0x17, 0xae, 0x70, 0x51, 0xb2, 0x7d, 0x92, 0x8c,
+	0x85, 0xde, 0xf3, 0x69, 0xc9, 0xf8, 0x0a, 0x0e, 0x42, 0xc4, 0xc1, 0x3c, 0xd8, 0xf7, 0x31, 0x3c,
+	0x6a, 0x32, 0xe1, 0xba, 0x3d, 0x58, 0x27, 0x5a, 0xcc, 0xc0, 0xc1, 0x4c, 0x4a, 0xd6, 0xc5, 0x02,
+	0xd6, 0x0d, 0xf5, 0x2d, 0x9b, 0x9a, 0x32, 0x80, 0x3e, 0xec, 0x62, 0x1b, 0xab, 0x2d, 0x09, 0xc1,
+	0x18, 0x56, 0x96, 0x9a, 0x32, 0x7e, 0x42, 0x35, 0xe5, 0x16, 0xb7, 0xd2, 0x6f, 0x27, 0xb4, 0x21,
+	0xa9, 0xeb, 0x84, 0x47, 0x22, 0x16, 0xd3, 0x08, 0xd8, 0x4f, 0xc3, 0xb5, 0x44, 0xc3, 0xb3, 0xba,
+	0x81, 0x9f, 0xe4, 0x35, 0x99, 0xd2, 0x12, 0x33, 0x70, 0x30, 0x93, 0x92, 0xe9, 0xe7, 0x3b, 0x54,
+	0xb7, 0x83, 0x9d, 0x24, 0xc3, 0xa9, 0xa4, 0x7e, 0x7e, 0xbb, 0x1f, 0x05, 0xb3, 0xe8, 0x32, 0x17,
+	0xa4, 0x73, 0x4f, 0xa7, 0x5a, 0xf5, 0xcf, 0x4a, 0xf0, 0xfc, 0x2d, 0x2a, 0xce, 0x44, 0x38, 0xed,
+	0x75, 0xab, 0x4b, 0x6d, 0xcb, 0xa1, 0xb1, 0x1a, 0x91, 0x3f, 0x53, 0x80, 0x71, 0x61, 0x17, 0x91,
+	0xa7, 0x19, 0xf2, 0xba, 0x87, 0x32, 0xc2, 0x95, 0x22, 0x65, 0x55, 0x58, 0x63, 0xe4, 0x4e, 0x28,
+	0x21, 0xf7, 0x13, 0x8b, 0xcc, 0x71, 0x74, 0x93, 0x6f, 0x97, 0xe0, 0x0a, 0xfb, 0x9e, 0x2a, 0x98,
+	0xf2, 0x13, 0xb3, 0xd8, 0x47, 0xf0, 0x11, 0x7e, 0xb5, 0x02, 0x17, 0x6e, 0xd1, 0xa0, 0x4f, 0xbb,
+	0xfe, 0x23, 0xda, 0xfc, 0x6b, 0x70, 0x21, 0x0a, 0xee, 0x6d, 0x05, 0xae, 0x27, 0x74, 0xb3, 0x94,
+	0xf5, 0xa3, 0xd5, 0x8f, 0x82, 0x59, 0x74, 0xe4, 0xab, 0xf0, 0xac, 0x2f, 0xa6, 0x2b, 0x61, 0x6f,
+	0x17, 0xc6, 0xa1, 0xd8, 0x01, 0xbb, 0x19, 0xc9, 0xf2, 0xd9, 0x56, 0x36, 0x1a, 0x0e, 0xa2, 0x27,
+	0xdf, 0x82, 0xf1, 0xae, 0x9c, 0x02, 0xd9, 0x37, 0xcb, 0x1d, 0x14, 0xb6, 0x1e, 0x63, 0x16, 0xcd,
+	0x71, 0xf1, 0x52, 0x4c, 0x08, 0xcc, 0xec, 0xa9, 0xb5, 0x53, 0xec, 0xa9, 0xff, 0xbd, 0x08, 0xd5,
+	0x5b, 0x9e, 0xdb, 0xeb, 0x36, 0xf7, 0x49, 0x1b, 0xc6, 0x1e, 0x71, 0x67, 0xa8, 0x9c, 0xe1, 0x87,
+	0x3f, 0x20, 0x23, 0x7c, 0xaa, 0x91, 0x8a, 0x2b, 0x9e, 0x51, 0xb2, 0x67, 0x9d, 0x78, 0x97, 0xee,
+	0x53, 0x53, 0xfa, 0x44, 0xc3, 0x4e, 0x7c, 0x87, 0x15, 0xa2, 0x80, 0x91, 0x0e, 0x4c, 0xe9, 0xb6,
+	0xed, 0x3e, 0xa2, 0xe6, 0xaa, 0x1e, 0xf0, 0x38, 0x06, 0xe9, 0x2b, 0x3b, 0xa9, 0x9b, 0x81, 0x07,
+	0xa7, 0x2c, 0x24, 0x59, 0x61, 0x9a, 0x37, 0x79, 0x0f, 0xaa, 0x7e, 0xe0, 0x7a, 0x4a, 0x79, 0x6e,
+	0xcc, 0x2f, 0x0e, 0xff, 0xd1, 0x9b, 0x6f, 0xb5, 0x04, 0x2b, 0xe1, 0x83, 0x91, 0x0f, 0xa8, 0x04,
+	0x68, 0xbf, 0x52, 0x00, 0xb8, 0xbd, 0xb1, 0xb1, 0x2e, 0xdd, 0x45, 0x26, 0x94, 0xf5, 0x5e, 0xe8,
+	0x78, 0x1e, 0xde, 0xc1, 0x9b, 0x88, 0x15, 0x97, 0x3e, 0xd9, 0x5e, 0xb0, 0x83, 0x9c, 0x3b, 0xf9,
+	0x49, 0xa8, 0xca, 0x0d, 0x8f, 0x6c, 0xf6, 0x30, 0x3e, 0x46, 0xae, 0xc4, 0xa8, 0xe0, 0xda, 0x6f,
+	0x14, 0x01, 0x56, 0x4c, 0x9b, 0xb6, 0xd4, 0x99, 0xa6, 0x7a, 0xb0, 0xe3, 0x51, 0x7f, 0xc7, 0xb5,
+	0xcd, 0x21, 0xbd, 0xe3, 0xdc, 0x87, 0xb3, 0xa1, 0x98, 0x60, 0xc4, 0x8f, 0x98, 0x30, 0xee, 0x07,
+	0xb4, 0xab, 0xc2, 0xc7, 0x87, 0x74, 0x8a, 0x9d, 0x13, 0x76, 0xae, 0x88, 0x0f, 0x26, 0xb8, 0x12,
+	0x1d, 0x1a, 0x96, 0x63, 0x88, 0x01, 0xd2, 0xdc, 0x1f, 0xb2, 0x23, 0x4d, 0xb1, 0x1d, 0xe4, 0x4a,
+	0xc4, 0x06, 0xe3, 0x3c, 0xb5, 0xdf, 0x2d, 0xc2, 0x65, 0x2e, 0x8f, 0x55, 0x23, 0xa1, 0xe2, 0x90,
+	0x3f, 0xd9, 0x77, 0x82, 0xfc, 0x8f, 0x1f, 0x4f, 0xb4, 0x38, 0x80, 0xbc, 0x46, 0x03, 0x3d, 0xd2,
+	0xcf, 0xa3, 0xb2, 0xd8, 0xb1, 0xf1, 0x1e, 0x94, 0x7d, 0x36, 0x5f, 0x89, 0xd6, 0x6b, 0x0d, 0xdd,
+	0x85, 0xb2, 0x5f, 0x80, 0xcf, 0x5e, 0x61, 0x14, 0x00, 0x9f, 0xb5, 0xb8, 0x38, 0xf2, 0x4d, 0x18,
+	0xf3, 0x03, 0x3d, 0xe8, 0xa9, 0xa1, 0xb9, 0x39, 0x6a, 0xc1, 0x9c, 0x79, 0x34, 0x8f, 0x88, 0x67,
+	0x94, 0x42, 0xb5, 0xdf, 0x2d, 0xc0, 0xd5, 0x6c, 0xc2, 0x55, 0xcb, 0x0f, 0xc8, 0x9f, 0xe8, 0x6b,
+	0xf6, 0x63, 0x7e, 0x71, 0x46, 0xcd, 0x1b, 0x3d, 0x3c, 0xa2, 0xa3, 0x4a, 0x62, 0x4d, 0x1e, 0x40,
+	0xc5, 0x0a, 0x68, 0x47, 0xd9, 0x0b, 0xee, 0x8d, 0xf8, 0xd5, 0x63, 0x4b, 0x3b, 0x93, 0x82, 0x42,
+	0x98, 0xf6, 0xdd, 0xe2, 0xa0, 0x57, 0xe6, 0xcb, 0x87, 0x9d, 0x8c, 0xfa, 0xbf, 0x93, 0x2f, 0xea,
+	0x3f, 0x59, 0xa1, 0xfe, 0xe0, 0xff, 0x3f, 0xd5, 0x1f, 0xfc, 0x7f, 0x2f, 0x7f, 0xf0, 0x7f, 0xaa,
+	0x19, 0x06, 0x9e, 0x01, 0xf8, 0x61, 0x09, 0x9e, 0x3b, 0xaa, 0xdb, 0xb0, 0xf5, 0x4c, 0xf6, 0xce,
+	0xbc, 0xeb, 0xd9, 0xd1, 0xfd, 0x90, 0xcc, 0x43, 0xa5, 0xbb, 0xa3, 0xfb, 0x4a, 0x29, 0x7b, 0x2e,
+	0x0c, 0x1b, 0x65, 0x85, 0x4f, 0xd8, 0xa4, 0xc1, 0x95, 0x39, 0xfe, 0x88, 0x02, 0x95, 0x4d, 0xc7,
+	0x1d, 0xea, 0xfb, 0x91, 0x8d, 0x27, 0x9c, 0x8e, 0xd7, 0x44, 0x31, 0x2a, 0x38, 0x09, 0x60, 0x4c,
+	0xb8, 0x0c, 0xe4, 0xca, 0x34, 0xda, 0x9d, 0x57, 0xf8, 0x52, 0x72, 0xcf, 0x25, 0x65, 0x91, 0x59,
+	0x28, 0x07, 0x51, 0xd8, 0xbe, 0x32, 0xb5, 0x94, 0x33, 0xf4, 0x53, 0x8e, 0x47, 0xde, 0x04, 0xe2,
+	0x6e, 0x71, 0x27, 0x89, 0x29, 0xe3, 0x21, 0x2c, 0xd7, 0xe1, 0x0a, 0x59, 0x29, 0x32, 0xd4, 0xdc,
+	0xeb, 0xc3, 0xc0, 0x0c, 0x2a, 0xed, 0x5f, 0xd4, 0xe0, 0x72, 0x76, 0x7f, 0x60, 0xed, 0xb6, 0x47,
+	0x3d, 0x9f, 0xf1, 0x2e, 0x24, 0xdb, 0xed, 0xbe, 0x28, 0x46, 0x05, 0xff, 0x58, 0x07, 0x10, 0xfe,
+	0x6a, 0x01, 0xae, 0x78, 0xd2, 0xe7, 0x77, 0x16, 0x41, 0x84, 0xcf, 0x0b, 0xf3, 0xd4, 0x00, 0x81,
+	0x38, 0xb8, 0x2e, 0xe4, 0xaf, 0x17, 0x60, 0xba, 0x93, 0xb2, 0x5b, 0x9d, 0xe2, 0x11, 0x62, 0x7e,
+	0x2e, 0x66, 0x6d, 0x80, 0x3c, 0x1c, 0x58, 0x13, 0xf2, 0x2d, 0x68, 0x74, 0x59, 0xbf, 0xf0, 0x03,
+	0xea, 0x18, 0x2a, 0xe0, 0x77, 0xf8, 0x91, 0xb4, 0x1e, 0xf1, 0x0a, 0x8f, 0x10, 0x72, 0xfd, 0x20,
+	0x06, 0xc0, 0xb8, 0xc4, 0xa7, 0xfc, 0xcc, 0xf0, 0x0d, 0xa8, 0xf9, 0x34, 0x08, 0x2c, 0xa7, 0x2d,
+	0xf6, 0x1b, 0x75, 0x31, 0x56, 0x5a, 0xb2, 0x0c, 0x43, 0x28, 0xf9, 0x29, 0xa8, 0x73, 0x17, 0xe2,
+	0x82, 0xd7, 0xf6, 0xa7, 0xeb, 0x3c, 0xfc, 0x6f, 0x42, 0x04, 0x34, 0xca, 0x42, 0x8c, 0xe0, 0xe4,
+	0x73, 0x30, 0xbe, 0xc5, 0x87, 0xaf, 0x34, 0x1d, 0x09, 0x9b, 0x25, 0xd7, 0xd6, 0x9a, 0xb1, 0x72,
+	0x4c, 0x60, 0x91, 0x79, 0x00, 0x1a, 0xfa, 0x59, 0xd3, 0xf6, 0xc9, 0xc8, 0x03, 0x8b, 0x31, 0x2c,
+	0xf2, 0x3c, 0x94, 0x02, 0xdb, 0xe7, 0x36, 0xc9, 0x5a, 0xb4, 0x05, 0xdd, 0x58, 0x6d, 0x21, 0x2b,
+	0xd7, 0xfe, 0xb0, 0x00, 0x53, 0xa9, 0xe3, 0x65, 0x8c, 0xa4, 0xe7, 0xd9, 0x72, 0x1a, 0x09, 0x49,
+	0x36, 0x71, 0x15, 0x59, 0x39, 0x79, 0x57, 0xaa, 0xe5, 0xc5, 0x9c, 0x39, 0x7f, 0xee, 0xea, 0x81,
+	0xcf, 0xf4, 0xf0, 0x3e, 0x8d, 0x9c, 0xbb, 0x6d, 0xa3, 0xfa, 0xc8, 0x75, 0x20, 0xe6, 0xb6, 0x8d,
+	0x60, 0x98, 0xc0, 0x4c, 0x19, 0x70, 0xcb, 0xc7, 0x31, 0xe0, 0x6a, 0xbf, 0x58, 0x8c, 0xb5, 0x80,
+	0xd4, 0xec, 0x3f, 0xa4, 0x05, 0x5e, 0x62, 0x0b, 0x68, 0xb8, 0xb8, 0xd7, 0xe3, 0xeb, 0x1f, 0x5f,
+	0x8c, 0x25, 0x94, 0x3c, 0x10, 0x6d, 0x5f, 0xca, 0x99, 0x97, 0x60, 0x63, 0xb5, 0x25, 0xa2, 0xe5,
+	0xd4, 0x57, 0x0b, 0x3f, 0x41, 0xf9, 0x94, 0x3e, 0x81, 0xf6, 0x4f, 0x4a, 0xd0, 0x78, 0xd3, 0xdd,
+	0xfa, 0x98, 0x44, 0xc4, 0x67, 0x2f, 0x53, 0xc5, 0x8f, 0x70, 0x99, 0xda, 0x84, 0x67, 0x83, 0xc0,
+	0x6e, 0x51, 0xc3, 0x75, 0x4c, 0x7f, 0x61, 0x3b, 0xa0, 0xde, 0xb2, 0xe5, 0x58, 0xfe, 0x0e, 0x35,
+	0xa5, 0x7b, 0xf0, 0x53, 0x87, 0x07, 0x33, 0xcf, 0x6e, 0x6c, 0xac, 0x66, 0xa1, 0xe0, 0x20, 0x5a,
+	0x3e, 0x6d, 0x88, 0xe3, 0xc9, 0xfc, 0xac, 0x9c, 0x8c, 0xa1, 0x12, 0xd3, 0x46, 0xac, 0x1c, 0x13,
+	0x58, 0xda, 0xbf, 0x2b, 0x42, 0x3d, 0xcc, 0xe6, 0x42, 0x5e, 0x84, 0xea, 0x96, 0xe7, 0xee, 0x52,
+	0x4f, 0x78, 0x62, 0xe5, 0x59, 0xb9, 0xa6, 0x28, 0x42, 0x05, 0x23, 0x2f, 0x40, 0x25, 0x70, 0xbb,
+	0x96, 0x91, 0x36, 0xa8, 0x6d, 0xb0, 0x42, 0x14, 0x30, 0x3e, 0x10, 0x78, 0x98, 0x28, 0x7f, 0xab,
+	0x5a, 0x6c, 0x20, 0xf0, 0x52, 0x94, 0x50, 0x35, 0x10, 0xca, 0x23, 0x1f, 0x08, 0x2f, 0x85, 0x2a,
+	0x60, 0x25, 0x39, 0x12, 0x53, 0x4a, 0xdb, 0x3b, 0x50, 0xf6, 0x75, 0xdf, 0x96, 0xcb, 0x5b, 0x8e,
+	0xf4, 0x23, 0x0b, 0xad, 0x55, 0x99, 0x7e, 0x64, 0xa1, 0xb5, 0x8a, 0x9c, 0xa9, 0xf6, 0x1b, 0x25,
+	0x68, 0x88, 0xf6, 0x15, 0xb3, 0xc7, 0x28, 0x5b, 0xf8, 0x75, 0x1e, 0x42, 0xe3, 0xf7, 0x3a, 0xd4,
+	0xe3, 0xe6, 0x28, 0x39, 0x19, 0xc6, 0xfd, 0x42, 0x11, 0x30, 0x0c, 0xa3, 0x89, 0x8a, 0x7e, 0xbc,
+	0x9b, 0x9e, 0x2d, 0x15, 0x3c, 0x23, 0x91, 0xd4, 0x71, 0x65, 0x64, 0x6c, 0xb8, 0x54, 0xdc, 0x89,
+	0xc1, 0x30, 0x81, 0xa9, 0xfd, 0x5e, 0x11, 0xea, 0xab, 0xd6, 0x36, 0x35, 0xf6, 0x0d, 0x9b, 0x92,
+	0x6f, 0xc0, 0x55, 0x93, 0xda, 0x94, 0xad, 0x98, 0xb7, 0x3c, 0xdd, 0xa0, 0xeb, 0xd4, 0xb3, 0x78,
+	0x46, 0x35, 0x36, 0x06, 0x65, 0xc0, 0xf2, 0xb5, 0xc3, 0x83, 0x99, 0xab, 0x4b, 0x03, 0xb1, 0xf0,
+	0x08, 0x0e, 0x64, 0x05, 0xc6, 0x4d, 0xea, 0x5b, 0x1e, 0x35, 0xd7, 0x63, 0x1b, 0xa2, 0x17, 0x55,
+	0x3d, 0x97, 0x62, 0xb0, 0x27, 0x07, 0x33, 0x13, 0xca, 0x10, 0x2a, 0x76, 0x46, 0x09, 0x52, 0x36,
+	0xb5, 0x74, 0xf5, 0x9e, 0x4f, 0x33, 0xea, 0x59, 0xe2, 0xf5, 0xe4, 0x53, 0xcb, 0x7a, 0x36, 0x0a,
+	0x0e, 0xa2, 0x25, 0x5b, 0x30, 0xcd, 0xeb, 0x9f, 0xc5, 0xb7, 0xcc, 0xf9, 0xbe, 0x74, 0x78, 0x30,
+	0xa3, 0x2d, 0xd1, 0xae, 0x47, 0x0d, 0x3d, 0xa0, 0xe6, 0xd2, 0x00, 0x6c, 0x1c, 0xc8, 0x47, 0xab,
+	0x40, 0x69, 0xd5, 0x6d, 0x6b, 0xdf, 0x2d, 0x41, 0x98, 0xe2, 0x8f, 0xfc, 0xd9, 0x02, 0x34, 0x74,
+	0xc7, 0x71, 0x03, 0x99, 0x3e, 0x4f, 0x44, 0x87, 0x60, 0xee, 0x4c, 0x82, 0xb3, 0x0b, 0x11, 0x53,
+	0x11, 0x58, 0x10, 0x06, 0x3b, 0xc4, 0x20, 0x18, 0x97, 0x4d, 0x7a, 0xa9, 0x58, 0x87, 0xb5, 0xfc,
+	0xb5, 0x38, 0x46, 0x64, 0xc3, 0xd5, 0xaf, 0xc0, 0xb9, 0x74, 0x65, 0x4f, 0xe2, 0xaa, 0xcc, 0x15,
+	0x34, 0x52, 0x04, 0x88, 0xe2, 0x9d, 0xce, 0xc0, 0x20, 0x67, 0x25, 0x0c, 0x72, 0xc3, 0x67, 0x29,
+	0x89, 0x2a, 0x3d, 0xd0, 0x08, 0xf7, 0x30, 0x65, 0x84, 0x5b, 0x19, 0x85, 0xb0, 0xa3, 0x0d, 0x6f,
+	0x5b, 0x70, 0x21, 0xc2, 0x8d, 0x66, 0x97, 0x3b, 0xa9, 0xd1, 0x2f, 0xf4, 0xca, 0xcf, 0x0c, 0x18,
+	0xfd, 0x53, 0xb1, 0x00, 0xb4, 0xfe, 0xf1, 0xaf, 0xfd, 0x8d, 0x02, 0x9c, 0x8b, 0x0b, 0xe1, 0x39,
+	0x05, 0x3e, 0x0f, 0x13, 0x1e, 0xd5, 0xcd, 0xa6, 0x1e, 0x18, 0x3b, 0xfc, 0xa8, 0x43, 0x81, 0x9f,
+	0x4d, 0xe0, 0xa7, 0x1f, 0x31, 0x0e, 0xc0, 0x24, 0x1e, 0xd1, 0xa1, 0xc1, 0x0a, 0x64, 0x8a, 0x95,
+	0x21, 0xad, 0xcc, 0x7c, 0x83, 0x87, 0x11, 0x1b, 0x8c, 0xf3, 0xd4, 0x7e, 0x58, 0x80, 0xc9, 0x78,
+	0x85, 0x4f, 0xdd, 0x02, 0xb9, 0x93, 0xb4, 0x40, 0x2e, 0x8e, 0xe0, 0xbb, 0x0f, 0xb0, 0x3a, 0x7e,
+	0xbb, 0x11, 0x7f, 0x35, 0x6e, 0x69, 0x8c, 0x1b, 0x57, 0x0a, 0x47, 0x1a, 0x57, 0x3e, 0xfe, 0x79,
+	0xd7, 0x06, 0xed, 0x0a, 0xca, 0x4f, 0xf1, 0xae, 0xe0, 0xa3, 0x4c, 0xde, 0x16, 0x4b, 0x40, 0x36,
+	0x96, 0x23, 0x01, 0x59, 0x27, 0x4c, 0x40, 0x56, 0x1d, 0xd9, 0xc4, 0x76, 0x9c, 0x24, 0x64, 0xb5,
+	0x33, 0x4d, 0x42, 0x56, 0x3f, 0xad, 0x24, 0x64, 0x90, 0x37, 0x09, 0xd9, 0x77, 0x0a, 0x30, 0x69,
+	0x26, 0x4e, 0x8c, 0xcb, 0x5c, 0x0d, 0xc3, 0x2f, 0x67, 0xc9, 0x03, 0xe8, 0xe2, 0xc8, 0x60, 0xb2,
+	0x0c, 0x53, 0x22, 0xb3, 0x52, 0x7f, 0x8d, 0x7f, 0x24, 0xa9, 0xbf, 0xc8, 0x37, 0xa1, 0x6e, 0xab,
+	0xb5, 0x4e, 0xa6, 0x74, 0x5d, 0x1d, 0x49, 0x97, 0x94, 0x3c, 0xa3, 0x53, 0x29, 0x61, 0x11, 0x46,
+	0x12, 0xb5, 0xff, 0x55, 0x8d, 0x2f, 0x88, 0x67, 0xed, 0xe3, 0x78, 0x35, 0xe9, 0xe3, 0xb8, 0x9e,
+	0xf6, 0x71, 0xf4, 0xad, 0xe6, 0xd2, 0xcf, 0xf1, 0xd9, 0xd8, 0x3a, 0x51, 0xe2, 0x79, 0xc0, 0xc2,
+	0x2e, 0x97, 0xb1, 0x56, 0x2c, 0xc0, 0x94, 0x54, 0x02, 0x14, 0x90, 0x4f, 0xb2, 0x13, 0x51, 0x94,
+	0xe1, 0x52, 0x12, 0x8c, 0x69, 0x7c, 0x26, 0xd0, 0x57, 0xc9, 0xb3, 0xc5, 0x8e, 0x2d, 0xea, 0xe3,
+	0x2a, 0xb1, 0x75, 0x88, 0xc1, 0x76, 0x77, 0x1e, 0xd5, 0x7d, 0xe9, 0xa9, 0x88, 0xed, 0xee, 0x90,
+	0x97, 0xa2, 0x84, 0xc6, 0xdd, 0x35, 0xd5, 0x0f, 0x71, 0xd7, 0xe8, 0xd0, 0xb0, 0x75, 0x3f, 0x10,
+	0x9d, 0xc9, 0x94, 0xb3, 0xc9, 0x1f, 0x3b, 0xde, 0xba, 0xcf, 0x74, 0x89, 0x48, 0x81, 0x5f, 0x8d,
+	0xd8, 0x60, 0x9c, 0x27, 0x31, 0x61, 0x9c, 0x3d, 0xf2, 0x99, 0xc5, 0x5c, 0x08, 0x64, 0x82, 0xc6,
+	0x93, 0xc8, 0x08, 0xb7, 0x8e, 0xab, 0x31, 0x3e, 0x98, 0xe0, 0x3a, 0xc0, 0xa3, 0x03, 0xc3, 0x78,
+	0x74, 0xc8, 0x97, 0x84, 0xe2, 0xb6, 0x1f, 0x7e, 0xd6, 0x06, 0xff, 0xac, 0x61, 0x84, 0x32, 0xc6,
+	0x81, 0x98, 0xc4, 0x65, 0xbd, 0xa2, 0x27, 0x9b, 0x41, 0x91, 0x8f, 0x27, 0x7b, 0xc5, 0x66, 0x12,
+	0x8c, 0x69, 0x7c, 0xb2, 0x0e, 0x17, 0xc3, 0xa2, 0x78, 0x35, 0x26, 0x38, 0x9f, 0x30, 0x64, 0x74,
+	0x33, 0x03, 0x07, 0x33, 0x29, 0xf9, 0x19, 0xac, 0x9e, 0xe7, 0x51, 0x27, 0xb8, 0xad, 0xfb, 0x3b,
+	0x32, 0xf6, 0x34, 0x3a, 0x83, 0x15, 0x81, 0x30, 0x8e, 0x47, 0xe6, 0x01, 0x04, 0x3b, 0x4e, 0x35,
+	0x95, 0x0c, 0xef, 0xde, 0x0c, 0x21, 0x18, 0xc3, 0xd2, 0xbe, 0x53, 0x87, 0xc6, 0x5d, 0x3d, 0xb0,
+	0xf6, 0x28, 0x77, 0xbf, 0x9e, 0x8e, 0x0f, 0xec, 0xaf, 0x14, 0xe0, 0x72, 0x32, 0x66, 0xfa, 0x14,
+	0x1d, 0x61, 0x3c, 0x67, 0x17, 0x66, 0x4a, 0xc3, 0x01, 0xb5, 0xe0, 0x2e, 0xb1, 0xbe, 0x10, 0xec,
+	0xd3, 0x76, 0x89, 0xb5, 0x06, 0x09, 0xc4, 0xc1, 0x75, 0xf9, 0xb8, 0xb8, 0xc4, 0x9e, 0xee, 0x1c,
+	0xbb, 0x29, 0x87, 0x5d, 0xf5, 0xa9, 0x71, 0xd8, 0xd5, 0x9e, 0x0a, 0xad, 0xbf, 0x1b, 0x73, 0xd8,
+	0xd5, 0x73, 0x06, 0x8e, 0xc9, 0x63, 0x46, 0x82, 0xdb, 0x20, 0xc7, 0x1f, 0xcf, 0x10, 0xa2, 0x1c,
+	0x29, 0x4c, 0x59, 0xde, 0xd2, 0x7d, 0xcb, 0x90, 0x6a, 0x47, 0x8e, 0xac, 0xe8, 0x2a, 0xd9, 0xa6,
+	0x88, 0x2f, 0xe1, 0x8f, 0x28, 0x78, 0x47, 0xe9, 0x4e, 0x8b, 0xb9, 0xd2, 0x9d, 0x92, 0x45, 0x28,
+	0x3b, 0xbb, 0x74, 0xff, 0x64, 0xb9, 0x36, 0xf8, 0x26, 0xf0, 0xee, 0x1d, 0xba, 0x8f, 0x9c, 0x58,
+	0xfb, 0x5e, 0x11, 0x80, 0xbd, 0xfe, 0xf1, 0x5c, 0x67, 0x3f, 0x09, 0x55, 0xbf, 0xc7, 0x0d, 0x43,
+	0x52, 0x61, 0x8a, 0xa2, 0xed, 0x44, 0x31, 0x2a, 0x38, 0x79, 0x01, 0x2a, 0x0f, 0x7b, 0xb4, 0xa7,
+	0xe2, 0x40, 0xc2, 0x7d, 0xc3, 0x5b, 0xac, 0x10, 0x05, 0xec, 0xf4, 0xcc, 0xdb, 0xca, 0xc5, 0x56,
+	0x39, 0x2d, 0x17, 0x5b, 0x1d, 0xaa, 0x77, 0x5d, 0x1e, 0xbc, 0xab, 0xfd, 0x97, 0x22, 0x40, 0x14,
+	0x1c, 0x49, 0x7e, 0xa5, 0x00, 0x97, 0xc2, 0x01, 0x17, 0x88, 0xed, 0x1f, 0xbf, 0x88, 0x20, 0xb7,
+	0xbb, 0x2d, 0x6b, 0xb0, 0xf3, 0x19, 0x68, 0x3d, 0x4b, 0x1c, 0x66, 0xd7, 0x82, 0x20, 0xd4, 0x68,
+	0xa7, 0x1b, 0xec, 0x2f, 0x59, 0x9e, 0xec, 0x81, 0x99, 0x31, 0xb8, 0x37, 0x25, 0x8e, 0x20, 0x95,
+	0x36, 0x0a, 0x3e, 0x88, 0x14, 0x04, 0x43, 0x3e, 0x64, 0x07, 0x6a, 0x8e, 0xfb, 0xae, 0xcf, 0x9a,
+	0x43, 0x76, 0xc7, 0xe1, 0x73, 0xe3, 0xcb, 0x66, 0x15, 0x6e, 0x17, 0xf9, 0x80, 0x55, 0x47, 0x36,
+	0xf6, 0x2f, 0x17, 0xe1, 0x42, 0x46, 0x3b, 0x90, 0x37, 0xe0, 0x9c, 0x8c, 0x43, 0x8d, 0x6e, 0xe4,
+	0x28, 0x44, 0x37, 0x72, 0xb4, 0x52, 0x30, 0xec, 0xc3, 0x26, 0xef, 0x02, 0xe8, 0x86, 0x41, 0x7d,
+	0x7f, 0xcd, 0x35, 0xd5, 0x7e, 0xe0, 0x75, 0xa6, 0xbe, 0x2c, 0x84, 0xa5, 0x4f, 0x0e, 0x66, 0x7e,
+	0x3a, 0x2b, 0xb4, 0x3c, 0xd5, 0xce, 0x11, 0x01, 0xc6, 0x58, 0x92, 0x6f, 0x00, 0x08, 0x1b, 0x40,
+	0x98, 0xcd, 0xe4, 0x43, 0x0c, 0x67, 0xb3, 0x2a, 0x59, 0xde, 0xec, 0x5b, 0x3d, 0xdd, 0x09, 0xac,
+	0x60, 0x5f, 0x24, 0x8f, 0xba, 0x1f, 0x72, 0xc1, 0x18, 0x47, 0xed, 0xb7, 0x8a, 0x50, 0x53, 0xae,
+	0x87, 0x33, 0xb0, 0x05, 0xb7, 0x13, 0xb6, 0xe0, 0x11, 0x05, 0x93, 0x67, 0x59, 0x82, 0xdd, 0x94,
+	0x25, 0xf8, 0x56, 0x7e, 0x51, 0x47, 0xdb, 0x81, 0x7f, 0xbd, 0x08, 0x93, 0x0a, 0x35, 0xaf, 0x85,
+	0xf6, 0xcb, 0x30, 0x25, 0x82, 0x40, 0xd6, 0xf4, 0xc7, 0x22, 0x8f, 0x16, 0x6f, 0xb0, 0xb2, 0x88,
+	0xdf, 0x6e, 0x26, 0x41, 0x98, 0xc6, 0x65, 0xdd, 0x5a, 0x14, 0x6d, 0xb2, 0x4d, 0x98, 0x70, 0x1b,
+	0x8b, 0xfd, 0x26, 0xef, 0xd6, 0xcd, 0x14, 0x0c, 0xfb, 0xb0, 0xd3, 0x26, 0xe2, 0xf2, 0x29, 0x98,
+	0x88, 0x7f, 0xa7, 0x00, 0xe3, 0x51, 0x7b, 0x9d, 0xba, 0x81, 0x78, 0x3b, 0x69, 0x20, 0x5e, 0xc8,
+	0xdd, 0x1d, 0x06, 0x98, 0x87, 0xff, 0x42, 0x15, 0x12, 0x67, 0x1a, 0xc8, 0x16, 0x5c, 0xb5, 0x32,
+	0x23, 0x33, 0x63, 0xb3, 0x4d, 0x98, 0x74, 0x61, 0x65, 0x20, 0x26, 0x1e, 0xc1, 0x85, 0xf4, 0xa0,
+	0xb6, 0x47, 0xbd, 0xc0, 0x32, 0xa8, 0x7a, 0xbf, 0x5b, 0xb9, 0x55, 0x32, 0x69, 0x04, 0x0f, 0xdb,
+	0xf4, 0xbe, 0x14, 0x80, 0xa1, 0x28, 0xb2, 0x05, 0x15, 0x6a, 0xb6, 0xa9, 0xca, 0x6c, 0x96, 0x33,
+	0xd3, 0x74, 0xd8, 0x9e, 0xec, 0xc9, 0x47, 0xc1, 0x9a, 0xf8, 0x71, 0x43, 0x53, 0x39, 0xa7, 0x82,
+	0x75, 0x4c, 0xf3, 0x12, 0xd9, 0x0d, 0xad, 0xad, 0x95, 0x11, 0x4d, 0x1e, 0x47, 0xd8, 0x5a, 0x7d,
+	0xa8, 0x3f, 0xd2, 0x03, 0xea, 0x75, 0x74, 0x6f, 0x57, 0xee, 0x36, 0x86, 0x7f, 0xc3, 0x07, 0x8a,
+	0x53, 0xf4, 0x86, 0x61, 0x11, 0x46, 0x72, 0x88, 0x0b, 0xf5, 0x40, 0xaa, 0xcf, 0xca, 0xa4, 0x3c,
+	0xbc, 0x50, 0xa5, 0x88, 0xfb, 0xf2, 0x6c, 0x83, 0x7a, 0xc4, 0x48, 0x06, 0xd9, 0x4b, 0xdc, 0xca,
+	0x20, 0xee, 0xe2, 0xc8, 0x71, 0xdb, 0x8e, 0x62, 0x15, 0x2d, 0x37, 0xd9, 0xb7, 0x3b, 0x68, 0xff,
+	0xa3, 0x12, 0x4d, 0xcb, 0x67, 0x6d, 0x27, 0xfc, 0x5c, 0xd2, 0x4e, 0x78, 0x2d, 0x6d, 0x27, 0x4c,
+	0xf9, 0xfc, 0x4f, 0x1e, 0x0d, 0x9d, 0x32, 0xaf, 0x95, 0x4f, 0xc1, 0xbc, 0xf6, 0x32, 0x34, 0xf6,
+	0xf8, 0x4c, 0x20, 0xd2, 0xa4, 0x55, 0xf8, 0x32, 0xc2, 0x67, 0xf6, 0xfb, 0x51, 0x31, 0xc6, 0x71,
+	0x18, 0x89, 0xbc, 0x49, 0x2b, 0xcc, 0x4c, 0x2e, 0x49, 0x5a, 0x51, 0x31, 0xc6, 0x71, 0x78, 0x20,
+	0xa5, 0xe5, 0xec, 0x0a, 0x82, 0x2a, 0x27, 0x10, 0x81, 0x94, 0xaa, 0x10, 0x23, 0x38, 0xb9, 0x01,
+	0xb5, 0x9e, 0xb9, 0x2d, 0x70, 0x6b, 0x1c, 0x97, 0x6b, 0x98, 0x9b, 0x4b, 0xcb, 0x32, 0x6d, 0x9b,
+	0x82, 0xb2, 0x9a, 0x74, 0xf4, 0xae, 0x02, 0xf0, 0xbd, 0xa1, 0xac, 0xc9, 0x5a, 0x54, 0x8c, 0x71,
+	0x1c, 0xf2, 0x45, 0x98, 0xf4, 0xa8, 0xd9, 0x33, 0x68, 0x48, 0x05, 0x9c, 0x4a, 0xe6, 0xb3, 0x8d,
+	0x43, 0x30, 0x85, 0x39, 0xc0, 0x48, 0xd8, 0x18, 0xca, 0x48, 0xf8, 0x15, 0x98, 0x34, 0x3d, 0xdd,
+	0x72, 0xa8, 0x79, 0xcf, 0xe1, 0x81, 0x1d, 0x32, 0x9c, 0x33, 0x34, 0xd0, 0x2f, 0x25, 0xa0, 0x98,
+	0xc2, 0xd6, 0xfe, 0x69, 0x11, 0x2a, 0x22, 0xcb, 0xee, 0x0a, 0x5c, 0xb0, 0x1c, 0x2b, 0xb0, 0x74,
+	0x7b, 0x89, 0xda, 0xfa, 0x7e, 0x3c, 0xc0, 0xa5, 0xd2, 0x7c, 0x96, 0x6d, 0xb4, 0x57, 0xfa, 0xc1,
+	0x98, 0x45, 0xc3, 0x1a, 0x47, 0xde, 0xc7, 0xa1, 0xb8, 0x08, 0x3b, 0x9a, 0x48, 0xf1, 0x9e, 0x80,
+	0x60, 0x0a, 0x93, 0x29, 0x43, 0xdd, 0xbe, 0xc8, 0x95, 0x8a, 0x50, 0x86, 0x92, 0xc1, 0x24, 0x49,
+	0x3c, 0xae, 0xa4, 0xf7, 0xb8, 0x42, 0x1c, 0x1e, 0x9a, 0x92, 0x41, 0x70, 0x42, 0x49, 0x4f, 0xc1,
+	0xb0, 0x0f, 0x9b, 0x71, 0xd8, 0xd6, 0x2d, 0xbb, 0xe7, 0xd1, 0x88, 0x43, 0x25, 0xe2, 0xb0, 0x9c,
+	0x82, 0x61, 0x1f, 0xb6, 0xb6, 0x01, 0xb0, 0xde, 0xb3, 0x7d, 0x9d, 0x67, 0x54, 0x1a, 0xd9, 0xd5,
+	0x23, 0x7f, 0x50, 0x84, 0x71, 0xc1, 0x56, 0x6e, 0xa4, 0xe7, 0x01, 0x64, 0xe2, 0x26, 0xd3, 0xf4,
+	0xa4, 0x6e, 0x10, 0x4d, 0x70, 0x21, 0x04, 0x63, 0x58, 0xc7, 0x0b, 0x29, 0x7b, 0x0d, 0xc6, 0x55,
+	0x88, 0x18, 0x57, 0x3b, 0x52, 0xe1, 0xb5, 0x8b, 0x31, 0x18, 0x26, 0x30, 0xc9, 0x12, 0x6b, 0xfd,
+	0x2d, 0x91, 0x28, 0xc0, 0x72, 0x1d, 0x4e, 0x2d, 0x32, 0x6a, 0x84, 0x47, 0x2b, 0x5b, 0x29, 0x38,
+	0xf6, 0x51, 0x90, 0xcf, 0x42, 0xad, 0xa3, 0x3f, 0xde, 0x74, 0x74, 0x63, 0x57, 0x4e, 0x21, 0xa1,
+	0x5e, 0xb1, 0x26, 0xcb, 0x31, 0xc4, 0x20, 0xba, 0xdc, 0x87, 0x8f, 0xe5, 0x3d, 0x7c, 0x18, 0x7e,
+	0xb2, 0xbe, 0x9d, 0xf8, 0x7f, 0x2b, 0x00, 0xe9, 0x3f, 0xd7, 0x43, 0x76, 0x60, 0xcc, 0xe1, 0xc6,
+	0xe5, 0xdc, 0x57, 0x85, 0xc4, 0x6c, 0xd4, 0x62, 0xd5, 0x97, 0x05, 0x92, 0x3f, 0x71, 0xa0, 0x46,
+	0x1f, 0x07, 0xd4, 0x73, 0xc2, 0x73, 0x7e, 0xa3, 0xb9, 0x96, 0x44, 0x6c, 0xb6, 0x25, 0x67, 0x0c,
+	0x65, 0x68, 0xbf, 0x5f, 0x84, 0x46, 0x0c, 0xef, 0xc3, 0x6c, 0x36, 0x3c, 0x75, 0x8c, 0xb0, 0xe9,
+	0x6e, 0x7a, 0xb6, 0xec, 0x5b, 0xb1, 0xd4, 0x31, 0x12, 0x84, 0xab, 0x18, 0xc7, 0x63, 0x1d, 0xb8,
+	0xa3, 0xfb, 0x41, 0xa2, 0x97, 0x85, 0x1d, 0x78, 0x2d, 0x84, 0x60, 0x0c, 0x8b, 0x5c, 0x97, 0x17,
+	0xcb, 0x94, 0x93, 0x09, 0x76, 0x07, 0xdc, 0x1a, 0x53, 0x19, 0xc1, 0xad, 0x31, 0xa4, 0x0d, 0xe7,
+	0x54, 0xad, 0x15, 0xf4, 0x64, 0xe9, 0x57, 0xc5, 0xcc, 0x93, 0x62, 0x81, 0x7d, 0x4c, 0xb5, 0xef,
+	0x15, 0x60, 0x22, 0x61, 0x51, 0x14, 0xa9, 0x71, 0xd5, 0xa9, 0xb4, 0x44, 0x6a, 0xdc, 0xd8, 0x61,
+	0xb2, 0x97, 0x60, 0x4c, 0x34, 0x50, 0x3a, 0xd8, 0x5c, 0x34, 0x21, 0x4a, 0x28, 0x53, 0x15, 0xa4,
+	0xcf, 0x22, 0xad, 0x2a, 0x48, 0xa7, 0x06, 0x2a, 0xb8, 0x70, 0x05, 0x8a, 0xda, 0xc9, 0x96, 0x8e,
+	0xb9, 0x02, 0x45, 0x39, 0x86, 0x18, 0xda, 0xdf, 0xe3, 0xf5, 0x0e, 0xbc, 0xfd, 0xd0, 0x54, 0xd2,
+	0x86, 0xaa, 0x0c, 0x30, 0x96, 0x43, 0xe3, 0x8d, 0x1c, 0x66, 0x4e, 0xce, 0x47, 0x86, 0xc8, 0xea,
+	0xc6, 0xee, 0xbd, 0xed, 0x6d, 0x54, 0xdc, 0xc9, 0x4d, 0xa8, 0xbb, 0x8e, 0x9c, 0x92, 0xe5, 0xeb,
+	0x7f, 0x86, 0xa9, 0x02, 0xf7, 0x54, 0xe1, 0x93, 0x83, 0x99, 0xcb, 0xe1, 0x43, 0xa2, 0x92, 0x18,
+	0x51, 0x6a, 0x7f, 0xba, 0x00, 0x97, 0xd0, 0xb5, 0x6d, 0xcb, 0x69, 0x27, 0x5d, 0xd9, 0xc4, 0x86,
+	0x49, 0x31, 0xd3, 0xec, 0xe9, 0x96, 0xad, 0x6f, 0xd9, 0xf4, 0x43, 0x4d, 0x1d, 0xbd, 0xc0, 0xb2,
+	0x67, 0xc5, 0x4d, 0xc9, 0xb3, 0x2b, 0x4e, 0x70, 0xcf, 0x6b, 0x05, 0x9e, 0xe5, 0xb4, 0xc5, 0xb2,
+	0xb7, 0x96, 0xe0, 0x85, 0x29, 0xde, 0xda, 0xbf, 0x2d, 0x03, 0x0f, 0x5e, 0x25, 0x9f, 0x87, 0x7a,
+	0x87, 0x1a, 0x3b, 0xba, 0x63, 0xf9, 0x2a, 0xc9, 0xf8, 0x15, 0xf6, 0x5e, 0x6b, 0xaa, 0xf0, 0x09,
+	0xfb, 0x14, 0x0b, 0xad, 0x55, 0x7e, 0x8e, 0x2c, 0xc2, 0x25, 0x06, 0x8c, 0xb5, 0x7d, 0x5f, 0xef,
+	0x5a, 0xb9, 0x63, 0x86, 0x44, 0x52, 0x67, 0x31, 0x1d, 0x89, 0xff, 0x28, 0x59, 0x13, 0x03, 0x2a,
+	0x5d, 0x5b, 0xb7, 0x9c, 0xdc, 0x37, 0x7b, 0xb2, 0x37, 0x58, 0x67, 0x9c, 0xc4, 0x7a, 0xc7, 0xff,
+	0xa2, 0xe0, 0x4d, 0x7a, 0xd0, 0xf0, 0x0d, 0x4f, 0xef, 0xf8, 0x3b, 0xfa, 0xfc, 0x2b, 0xaf, 0xe6,
+	0xde, 0xcd, 0x45, 0xa2, 0x84, 0x72, 0xb9, 0x88, 0x0b, 0x6b, 0xad, 0xdb, 0x0b, 0xf3, 0xaf, 0xbc,
+	0x8a, 0x71, 0x39, 0x71, 0xb1, 0xaf, 0xbc, 0x3c, 0x9f, 0xff, 0xa6, 0xcf, 0x6c, 0xb1, 0xaf, 0xbc,
+	0x3c, 0x8f, 0x71, 0x39, 0xac, 0x49, 0xdd, 0xd8, 0x32, 0x96, 0x4f, 0xe0, 0xbd, 0xc8, 0x2d, 0xc0,
+	0xff, 0xa2, 0xe0, 0xad, 0xfd, 0xcf, 0x02, 0xd4, 0x43, 0x38, 0x9b, 0x28, 0x45, 0xba, 0xca, 0x95,
+	0xa5, 0x93, 0xe9, 0x26, 0x7c, 0xa2, 0x5c, 0x94, 0xa4, 0x18, 0x32, 0x21, 0xef, 0xc0, 0xb8, 0xf8,
+	0x2f, 0xd3, 0x47, 0x17, 0x4f, 0x9c, 0xa3, 0x7a, 0x31, 0x46, 0x8e, 0x09, 0x66, 0xe4, 0x4b, 0x30,
+	0xc1, 0xf5, 0xa0, 0x9b, 0x8e, 0xd9, 0x75, 0x2d, 0x79, 0xdb, 0x53, 0x2c, 0x53, 0xd7, 0x46, 0x1c,
+	0x88, 0x49, 0xdc, 0xf0, 0xc5, 0xf9, 0x97, 0x20, 0x9b, 0x00, 0x6c, 0xa5, 0x90, 0xb5, 0x3c, 0xd1,
+	0xab, 0x73, 0xe3, 0xe8, 0x66, 0x48, 0x8c, 0x31, 0x46, 0x19, 0x59, 0xc0, 0x8b, 0xa3, 0xce, 0x02,
+	0x3e, 0x07, 0xf5, 0x1d, 0xdd, 0x31, 0xfd, 0x1d, 0x7d, 0x97, 0xca, 0x13, 0x15, 0xe1, 0xce, 0xfd,
+	0xb6, 0x02, 0x60, 0x84, 0xa3, 0xfd, 0x83, 0x31, 0x10, 0x61, 0x54, 0x6c, 0x4a, 0x37, 0x2d, 0x5f,
+	0x9c, 0x7b, 0x2a, 0x70, 0xca, 0x70, 0x4a, 0x5f, 0x92, 0xe5, 0x18, 0x62, 0x90, 0x2b, 0x50, 0xea,
+	0x58, 0x8e, 0x54, 0xd8, 0xb9, 0xdf, 0x63, 0xcd, 0x72, 0x90, 0x95, 0x71, 0x90, 0xfe, 0x58, 0x2a,
+	0xe4, 0x02, 0xa4, 0x3f, 0x46, 0x56, 0x46, 0xbe, 0x0c, 0x53, 0xb6, 0xeb, 0xee, 0xb2, 0xc9, 0x39,
+	0x1e, 0x19, 0x3e, 0x21, 0x2c, 0x91, 0xab, 0x49, 0x10, 0xa6, 0x71, 0xc9, 0x26, 0x3c, 0xfb, 0x3e,
+	0xf5, 0x5c, 0xb9, 0x1a, 0xb5, 0x6c, 0x4a, 0xbb, 0x8a, 0x8d, 0x50, 0x03, 0x79, 0xe0, 0xfa, 0xd7,
+	0xb2, 0x51, 0x70, 0x10, 0x2d, 0x3f, 0x6a, 0xa3, 0x7b, 0x6d, 0x1a, 0xac, 0x7b, 0x2e, 0x53, 0xf5,
+	0x2d, 0xa7, 0xad, 0xd8, 0x8e, 0x45, 0x6c, 0x37, 0xb2, 0x51, 0x70, 0x10, 0x2d, 0x79, 0x1b, 0xa6,
+	0x05, 0x48, 0x28, 0x85, 0x0b, 0x62, 0x12, 0xb7, 0x6c, 0x75, 0xdd, 0xf8, 0x84, 0x70, 0x2f, 0x6f,
+	0x0c, 0xc0, 0xc1, 0x81, 0xd4, 0xe4, 0x4d, 0x38, 0xa7, 0x82, 0x0b, 0xd6, 0xa9, 0xd7, 0x0a, 0x43,
+	0xeb, 0x26, 0xd4, 0x09, 0x03, 0x15, 0x61, 0x8f, 0x29, 0x2c, 0xec, 0xa3, 0x23, 0x08, 0x97, 0x79,
+	0xfc, 0xdc, 0x66, 0x77, 0xd1, 0x75, 0x6d, 0xd3, 0x7d, 0xe4, 0xa8, 0x77, 0x17, 0xfb, 0x5b, 0x1e,
+	0x4f, 0xd0, 0xca, 0xc4, 0xc0, 0x01, 0x94, 0xec, 0xcd, 0x39, 0x64, 0xc9, 0x7d, 0xe4, 0xa4, 0xb9,
+	0x42, 0xf4, 0xe6, 0xad, 0x01, 0x38, 0x38, 0x90, 0x9a, 0x2c, 0x03, 0x49, 0xbf, 0xc1, 0x66, 0x57,
+	0x46, 0xbc, 0x5c, 0x16, 0xf9, 0xea, 0xd2, 0x50, 0xcc, 0xa0, 0x20, 0xab, 0x70, 0x31, 0x5d, 0xca,
+	0xc4, 0xc9, 0xe0, 0x17, 0x9e, 0xa9, 0x1e, 0x33, 0xe0, 0x98, 0x49, 0xa5, 0x35, 0xa0, 0x1e, 0x5e,
+	0xd8, 0xac, 0xfd, 0x9b, 0x22, 0x4c, 0xa5, 0x72, 0x7e, 0x9d, 0x81, 0x27, 0xc4, 0x49, 0x78, 0x42,
+	0x56, 0x73, 0x5d, 0x3c, 0x1d, 0xab, 0xf9, 0x40, 0x87, 0xc8, 0x5e, 0xca, 0x21, 0x72, 0x77, 0x64,
+	0x12, 0x8f, 0xf6, 0x8b, 0x1c, 0x16, 0xe0, 0x42, 0x8a, 0xe2, 0x0c, 0xcc, 0xfd, 0x9d, 0xa4, 0xb9,
+	0xff, 0xf6, 0xa8, 0x5e, 0x76, 0x80, 0xd5, 0xff, 0xff, 0xf4, 0xbf, 0x64, 0x4b, 0x78, 0xa1, 0xaa,
+	0x32, 0xbd, 0x52, 0xee, 0x0d, 0xa5, 0xca, 0xdf, 0xc4, 0xbe, 0x6f, 0x32, 0x5d, 0x8d, 0xd3, 0x46,
+	0x25, 0x85, 0xf8, 0x50, 0x53, 0x39, 0x94, 0x46, 0xeb, 0x63, 0x0b, 0x1b, 0x3b, 0x4c, 0x8b, 0x17,
+	0x0a, 0xd2, 0x7e, 0xa9, 0x04, 0x97, 0x32, 0x3b, 0xc5, 0xd9, 0x99, 0x5a, 0xbf, 0x94, 0x34, 0xb5,
+	0xbe, 0x98, 0x36, 0xb5, 0x5e, 0x4c, 0xd5, 0xef, 0x29, 0xb6, 0xb8, 0x8e, 0xd0, 0x8a, 0xa8, 0x4d,
+	0xc1, 0x44, 0x22, 0xef, 0x97, 0xf6, 0x7b, 0x15, 0x68, 0xc4, 0x7a, 0xd2, 0x53, 0x97, 0x6f, 0x89,
+	0x7c, 0x11, 0x26, 0x3b, 0x7e, 0x7b, 0x65, 0xe9, 0x36, 0xd5, 0x4d, 0xea, 0xa9, 0x63, 0xa6, 0x75,
+	0xb9, 0xd7, 0x4a, 0x40, 0x30, 0x85, 0x49, 0x56, 0xe1, 0x92, 0x47, 0x1f, 0xf6, 0xa8, 0x1f, 0x24,
+	0x6d, 0x91, 0x52, 0x65, 0x91, 0xcb, 0x4d, 0x0a, 0xc1, 0xc7, 0x6c, 0x22, 0x36, 0x85, 0x88, 0xd8,
+	0x84, 0x4a, 0xce, 0x71, 0xa4, 0xda, 0x9b, 0x07, 0x28, 0x88, 0xec, 0x4c, 0xb1, 0x12, 0x14, 0x52,
+	0x06, 0x1c, 0x5d, 0x18, 0xfb, 0x08, 0x8f, 0x2e, 0xc4, 0xe3, 0x25, 0xab, 0x47, 0xc6, 0x4b, 0x3e,
+	0xd5, 0xe1, 0x61, 0xda, 0xb7, 0x20, 0xd1, 0xe0, 0xc4, 0x85, 0x7a, 0xf8, 0xb2, 0xb9, 0x63, 0xb6,
+	0xa2, 0xe3, 0x03, 0xdc, 0x61, 0x11, 0x3e, 0x62, 0x24, 0x43, 0xdb, 0x66, 0xa3, 0xd0, 0xf7, 0x2d,
+	0xd7, 0x39, 0xdd, 0x3b, 0xb2, 0xff, 0x55, 0x11, 0xea, 0xa1, 0x1b, 0xec, 0x18, 0x17, 0x57, 0x25,
+	0x1a, 0xa2, 0x78, 0xfa, 0x0d, 0x11, 0x3f, 0x0c, 0x53, 0xca, 0x71, 0x18, 0xa6, 0x0b, 0xd5, 0xc0,
+	0xb3, 0xda, 0x6d, 0x69, 0x34, 0xcc, 0x73, 0x1a, 0x26, 0x6c, 0xae, 0x0d, 0xc1, 0x50, 0xb6, 0xac,
+	0x78, 0x40, 0x25, 0x46, 0x7b, 0x0f, 0xce, 0xa5, 0x31, 0xb9, 0x45, 0xcd, 0xd8, 0xa1, 0x66, 0xcf,
+	0x56, 0x6d, 0x1c, 0x59, 0xd4, 0x64, 0x39, 0x86, 0x18, 0x6c, 0x30, 0xb1, 0xcf, 0xf4, 0xbe, 0xeb,
+	0xa8, 0x35, 0x8a, 0x0f, 0xa6, 0x0d, 0x59, 0x86, 0x21, 0x54, 0xfb, 0xcf, 0x25, 0xb8, 0x12, 0x39,
+	0x33, 0xd7, 0x74, 0x47, 0x6f, 0x27, 0x03, 0x55, 0x3f, 0xc9, 0xca, 0x30, 0x92, 0xdb, 0x07, 0x4b,
+	0x4f, 0xc1, 0xed, 0x83, 0xff, 0xaf, 0x08, 0xfc, 0x70, 0x1d, 0xf9, 0x16, 0x8c, 0xab, 0xf6, 0x64,
+	0xcf, 0xf2, 0x73, 0xde, 0xcc, 0xfd, 0x39, 0xf9, 0x19, 0xbe, 0xd0, 0xcf, 0x13, 0x2f, 0xc5, 0x84,
+	0x40, 0xe2, 0x42, 0x6d, 0x5b, 0xb7, 0x6d, 0xb6, 0x79, 0xcf, 0xad, 0x38, 0x26, 0x84, 0xf3, 0x6e,
+	0xbe, 0x2c, 0x59, 0x63, 0x28, 0x84, 0x7c, 0xa7, 0x00, 0x13, 0x5e, 0xdc, 0x7a, 0x2b, 0x3f, 0x48,
+	0x9e, 0xd0, 0xdd, 0x18, 0xb7, 0xf8, 0x71, 0x8a, 0xb8, 0x89, 0x38, 0x29, 0x53, 0xfb, 0x4f, 0x05,
+	0x98, 0x68, 0xd9, 0x96, 0x69, 0x39, 0xed, 0x53, 0xbc, 0xfc, 0xf0, 0x1e, 0x54, 0x7c, 0xdb, 0x32,
+	0xe9, 0x90, 0x67, 0x6d, 0xb9, 0xd9, 0x8f, 0xd5, 0x92, 0x29, 0x0b, 0xec, 0x27, 0x79, 0x9b, 0x62,
+	0xe9, 0x18, 0xb7, 0x29, 0xfe, 0x56, 0x0d, 0xe4, 0x31, 0x51, 0xd2, 0x83, 0x7a, 0x5b, 0x5d, 0xd2,
+	0x26, 0xdf, 0xf1, 0x76, 0x8e, 0x04, 0xff, 0x89, 0xeb, 0xde, 0xc4, 0xdc, 0x1f, 0x16, 0x62, 0x24,
+	0x89, 0x50, 0xa8, 0xf0, 0x64, 0x0c, 0xb9, 0xbd, 0x5d, 0xb1, 0xb4, 0x1b, 0xa2, 0x65, 0x78, 0x01,
+	0x0a, 0xee, 0x44, 0x87, 0xf2, 0x4e, 0x10, 0x74, 0x65, 0x67, 0x1a, 0xde, 0x77, 0x18, 0xe5, 0x24,
+	0x15, 0xda, 0x2c, 0x7b, 0x46, 0xce, 0x9a, 0x89, 0x70, 0xf4, 0xf0, 0x26, 0xf9, 0xc5, 0x5c, 0x61,
+	0xc2, 0x71, 0x11, 0xec, 0x19, 0x39, 0x6b, 0xf2, 0x73, 0xd0, 0x08, 0x3c, 0xdd, 0xf1, 0xb7, 0x5d,
+	0xaf, 0x43, 0x3d, 0xa9, 0x81, 0x0e, 0x3f, 0x32, 0x36, 0x97, 0x36, 0x22, 0x6e, 0xc2, 0xe5, 0x9e,
+	0x28, 0xc2, 0xb8, 0x34, 0xb2, 0x0b, 0xb5, 0x9e, 0x29, 0x2a, 0x26, 0xf5, 0xcf, 0x85, 0x1c, 0x92,
+	0xe3, 0x41, 0xc0, 0xea, 0x09, 0x43, 0x01, 0xac, 0x37, 0x46, 0x79, 0x13, 0xab, 0x39, 0x7b, 0x63,
+	0x2a, 0xa7, 0xd3, 0xe0, 0x84, 0x89, 0xa4, 0x13, 0x6d, 0xcc, 0x6b, 0x39, 0x1b, 0x37, 0xb1, 0xc1,
+	0x92, 0x59, 0x6e, 0xd3, 0xdb, 0x72, 0x0b, 0xc6, 0xba, 0xdc, 0x19, 0x2d, 0xcf, 0x27, 0xdc, 0xcc,
+	0xe9, 0xd3, 0x8e, 0x9f, 0xfe, 0x16, 0x25, 0x28, 0x05, 0x90, 0xaf, 0x43, 0xc9, 0x7f, 0xe8, 0xcb,
+	0x4b, 0xcc, 0x73, 0x38, 0x1d, 0x1e, 0xaa, 0xbe, 0xc9, 0x0d, 0xc2, 0xad, 0x87, 0x3e, 0x32, 0xbe,
+	0xda, 0x3f, 0x2c, 0x40, 0x95, 0xc1, 0xd8, 0x9a, 0x31, 0x07, 0x75, 0xfd, 0x91, 0x8f, 0xb4, 0x1d,
+	0x9d, 0xbe, 0x0a, 0x67, 0xa1, 0x85, 0x07, 0x2d, 0x01, 0xc0, 0x08, 0x87, 0x11, 0xf0, 0x10, 0x7e,
+	0xee, 0x1d, 0x2e, 0x26, 0x09, 0xde, 0x52, 0x00, 0x8c, 0x70, 0xc8, 0x7d, 0xb8, 0xcc, 0x1f, 0xee,
+	0x3d, 0x72, 0xa8, 0xb7, 0xf0, 0xa0, 0xb5, 0x60, 0x18, 0x6e, 0x8f, 0xbb, 0x37, 0x4a, 0x89, 0x90,
+	0xaa, 0xcb, 0x6f, 0x65, 0x62, 0xe1, 0x00, 0x6a, 0xed, 0x77, 0xca, 0x50, 0x0f, 0xdf, 0xf0, 0xe3,
+	0xfb, 0x1e, 0x64, 0x11, 0xce, 0xef, 0x59, 0xbe, 0x25, 0xac, 0xcc, 0xf1, 0x68, 0xdd, 0x8a, 0x50,
+	0x91, 0xee, 0xa7, 0x81, 0xd8, 0x8f, 0x4f, 0x56, 0xe0, 0x42, 0x47, 0x7f, 0x7c, 0xb7, 0xd7, 0xd9,
+	0xa2, 0xde, 0xbd, 0x6d, 0x69, 0xf2, 0xf0, 0x65, 0x84, 0x0c, 0x0f, 0x10, 0x5a, 0xeb, 0x07, 0x63,
+	0x16, 0x0d, 0xf9, 0x32, 0x4c, 0x3d, 0xd2, 0x2d, 0xbe, 0x93, 0x8e, 0x1b, 0xe4, 0x2b, 0xc2, 0x5d,
+	0xf0, 0x20, 0x09, 0xc2, 0x34, 0x2e, 0x79, 0x19, 0x1a, 0x54, 0x3a, 0x78, 0x36, 0x3d, 0x5b, 0x9d,
+	0x2e, 0x3d, 0x3c, 0x98, 0x69, 0x28, 0xbf, 0x0f, 0x8f, 0x38, 0x88, 0xe1, 0x90, 0x2f, 0xc2, 0xa4,
+	0x1e, 0x04, 0x9e, 0xb5, 0xd5, 0x0b, 0x78, 0x53, 0x8b, 0xd8, 0x42, 0x69, 0x2f, 0x58, 0x48, 0x40,
+	0x30, 0x85, 0x49, 0xee, 0xc1, 0x25, 0x69, 0xd7, 0x49, 0x22, 0xca, 0x54, 0x7e, 0x5c, 0x9d, 0x5b,
+	0xcb, 0x42, 0xc0, 0x6c, 0x3a, 0xad, 0x03, 0xd2, 0x2e, 0x45, 0x8c, 0xc4, 0x7d, 0xd0, 0x22, 0xc1,
+	0xcd, 0xdc, 0xf1, 0x96, 0xfd, 0xf0, 0x62, 0xe2, 0xd8, 0x7d, 0x74, 0x99, 0x17, 0x3f, 0x6b, 0xff,
+	0xba, 0x08, 0xa5, 0x8d, 0xd5, 0x96, 0xb8, 0x63, 0x86, 0xdf, 0x30, 0x4f, 0x5b, 0xbb, 0x56, 0xf7,
+	0x3e, 0xf5, 0xac, 0xed, 0x7d, 0xe9, 0x12, 0x8a, 0xdd, 0x31, 0x93, 0xc6, 0xc0, 0x0c, 0x2a, 0xee,
+	0xf1, 0xd3, 0x17, 0xa9, 0x97, 0xc3, 0xe3, 0xb7, 0x10, 0x91, 0x63, 0x82, 0x19, 0xd9, 0x04, 0x30,
+	0x22, 0xd6, 0xa5, 0x13, 0xbb, 0xe9, 0x62, 0x8c, 0x63, 0x8c, 0x08, 0x42, 0x7d, 0x97, 0xa1, 0x72,
+	0xae, 0xe5, 0x93, 0x70, 0xe5, 0x0b, 0xc4, 0x1d, 0x45, 0x8b, 0x11, 0x1b, 0xcd, 0x81, 0x89, 0xc4,
+	0x25, 0xd1, 0xe4, 0x0b, 0x50, 0x73, 0xbb, 0x31, 0xad, 0xa9, 0xce, 0x0f, 0x45, 0xd6, 0xee, 0xc9,
+	0xb2, 0x27, 0x07, 0x33, 0x13, 0xab, 0x6e, 0xdb, 0x32, 0x54, 0x01, 0x86, 0xe8, 0x44, 0x83, 0x31,
+	0x9e, 0x7e, 0x47, 0x5d, 0x11, 0xcd, 0xa7, 0x6d, 0x7e, 0x8b, 0xab, 0x8f, 0x12, 0xa2, 0xfd, 0x7c,
+	0x19, 0xa2, 0xc0, 0x59, 0xe2, 0xc3, 0x98, 0x38, 0xfa, 0x2f, 0x15, 0xb4, 0x53, 0xcd, 0x32, 0x20,
+	0x45, 0x91, 0x36, 0x94, 0xde, 0x73, 0xb7, 0x72, 0xeb, 0x67, 0xb1, 0x1c, 0x82, 0x62, 0xec, 0xc6,
+	0x0a, 0x90, 0x49, 0x20, 0x7f, 0xb5, 0x00, 0xe7, 0xfd, 0xf4, 0x0e, 0x57, 0x76, 0x07, 0xcc, 0xbf,
+	0x95, 0x4f, 0xef, 0x99, 0xe5, 0xe9, 0xd5, 0x41, 0x60, 0xec, 0xaf, 0x0b, 0x6b, 0x7f, 0x11, 0xd1,
+	0x2a, 0xbb, 0xd3, 0xf0, 0xed, 0x2f, 0xa2, 0x64, 0x93, 0xed, 0x9f, 0x2c, 0x43, 0x29, 0x4a, 0xfb,
+	0x76, 0x11, 0x1a, 0x31, 0xa5, 0x2c, 0xf7, 0xcd, 0xe3, 0x8f, 0x53, 0x37, 0x8f, 0xaf, 0x0f, 0x1f,
+	0xe0, 0x1d, 0xd5, 0xea, 0xb4, 0x2f, 0x1f, 0xff, 0xc7, 0x45, 0x28, 0x6d, 0x2e, 0x2d, 0x9f, 0xb9,
+	0x91, 0x8e, 0xec, 0x40, 0x75, 0xab, 0x67, 0xd9, 0x81, 0xe5, 0xe4, 0xce, 0x72, 0xaa, 0x2e, 0x6a,
+	0x97, 0x11, 0x4e, 0x82, 0x2b, 0x2a, 0xf6, 0xa4, 0x0d, 0xd5, 0xb6, 0xb8, 0x66, 0x22, 0xf7, 0xb1,
+	0x37, 0x79, 0x5d, 0x85, 0x10, 0x24, 0x1f, 0x50, 0x71, 0xd7, 0xf6, 0x61, 0x6c, 0x73, 0x49, 0xee,
+	0xee, 0xcf, 0xd8, 0xe4, 0xf9, 0x73, 0x10, 0x2a, 0xfb, 0x67, 0x2f, 0xfc, 0xbf, 0x16, 0x20, 0xb9,
+	0xbf, 0x39, 0xfb, 0xde, 0xb4, 0x9b, 0xee, 0x4d, 0x4b, 0xa3, 0x18, 0x7c, 0xd9, 0x1d, 0x4a, 0xfb,
+	0x97, 0x05, 0x48, 0xe5, 0x6b, 0x21, 0xaf, 0xca, 0x8c, 0xe5, 0xc9, 0xf3, 0x45, 0x2a, 0x63, 0x39,
+	0x49, 0x62, 0xc7, 0x32, 0x97, 0x7f, 0x50, 0x80, 0x09, 0x2f, 0x1e, 0x36, 0x27, 0xab, 0x3f, 0xbc,
+	0xb7, 0x38, 0x33, 0x08, 0x4f, 0x9e, 0x81, 0x8b, 0x83, 0x30, 0x29, 0x57, 0xfb, 0xfb, 0x45, 0x18,
+	0x3b, 0xb3, 0x14, 0x75, 0x34, 0xe1, 0x8c, 0x5f, 0xcc, 0x39, 0xdb, 0x0f, 0xf4, 0xc1, 0x77, 0x52,
+	0x3e, 0xf8, 0x9b, 0x79, 0x05, 0x1d, 0xed, 0x7a, 0xff, 0xe7, 0x05, 0x90, 0x6b, 0xcd, 0x8a, 0xe3,
+	0x07, 0xba, 0x63, 0x50, 0x62, 0x84, 0x0b, 0x5b, 0x5e, 0x87, 0xac, 0x3c, 0x1f, 0x26, 0x74, 0x19,
+	0xfe, 0x5f, 0x2d, 0x64, 0xe4, 0xb3, 0x50, 0xdb, 0x71, 0xfd, 0xc0, 0x89, 0x76, 0x47, 0xa1, 0x65,
+	0xfc, 0xb6, 0x2c, 0xc7, 0x10, 0x23, 0x1d, 0xc4, 0x5a, 0x19, 0x1c, 0xc4, 0xaa, 0x7d, 0x0d, 0xa6,
+	0xd2, 0x79, 0xf6, 0x6e, 0x65, 0xe6, 0xd9, 0x7b, 0x61, 0x40, 0x9e, 0xbd, 0xc6, 0xe0, 0x1c, 0x7b,
+	0xbf, 0x56, 0x84, 0xf1, 0x8f, 0x4b, 0x7e, 0xbd, 0xac, 0x03, 0xa2, 0xa5, 0x9c, 0x07, 0x44, 0xcb,
+	0x27, 0x39, 0x20, 0xaa, 0xfd, 0xa0, 0x00, 0x70, 0x66, 0xc9, 0xfd, 0xcc, 0x64, 0x30, 0x47, 0xee,
+	0x3e, 0x9b, 0x1d, 0xc3, 0xf1, 0xb7, 0xc7, 0xd4, 0x2b, 0x71, 0xcf, 0xf8, 0x07, 0x05, 0x98, 0xd4,
+	0x13, 0x67, 0x21, 0x73, 0xeb, 0xe2, 0xa9, 0xa3, 0x95, 0xe1, 0x51, 0x9e, 0x64, 0x39, 0xa6, 0xc4,
+	0x92, 0xd7, 0xa2, 0x8b, 0xb8, 0x62, 0x06, 0x87, 0xbe, 0x1b, 0xb4, 0xc4, 0xe1, 0x8d, 0x38, 0xe6,
+	0x87, 0x9c, 0x3d, 0x2d, 0x8d, 0xe4, 0xec, 0x69, 0xdc, 0x4b, 0x5c, 0x3e, 0xd2, 0x4b, 0xbc, 0x07,
+	0xf5, 0x6d, 0xcf, 0xed, 0xf0, 0xe3, 0x9d, 0xd3, 0x15, 0xfe, 0x29, 0x6f, 0xe6, 0x58, 0x84, 0x3b,
+	0x5b, 0x96, 0x43, 0x4d, 0x7e, 0x74, 0x34, 0x34, 0xbe, 0x2c, 0x2b, 0xfe, 0x18, 0x89, 0xe2, 0xee,
+	0x42, 0x57, 0x48, 0x1d, 0x1b, 0xa5, 0xd4, 0x70, 0x9e, 0xda, 0x10, 0xdc, 0x51, 0x89, 0x49, 0x1e,
+	0xe9, 0xac, 0x9e, 0xd1, 0x91, 0xce, 0xfd, 0xf8, 0x49, 0xd9, 0x5a, 0x4e, 0x4b, 0xea, 0xc9, 0xd2,
+	0xb1, 0xfd, 0xf9, 0xaa, 0x9a, 0x3b, 0x9f, 0xba, 0xeb, 0x66, 0x3e, 0x49, 0xc3, 0xd6, 0xa6, 0x7d,
+	0x39, 0xd2, 0x6a, 0x67, 0x98, 0x23, 0xad, 0x3e, 0x9a, 0x1c, 0x69, 0x90, 0x2f, 0x47, 0x5a, 0x63,
+	0x44, 0x39, 0xd2, 0xc6, 0x47, 0x95, 0x23, 0x6d, 0x62, 0xa8, 0x1c, 0x69, 0x93, 0xc7, 0xca, 0x91,
+	0x76, 0x50, 0x82, 0x94, 0x8d, 0xe1, 0x93, 0xb0, 0x81, 0x1f, 0xab, 0xb0, 0x81, 0xef, 0x16, 0x21,
+	0x5a, 0x03, 0x4e, 0x78, 0x0e, 0xe0, 0x6d, 0x7e, 0x14, 0x93, 0x1f, 0xeb, 0x1d, 0x52, 0x35, 0x1d,
+	0x97, 0xc7, 0x36, 0x39, 0x0f, 0x0c, 0xb9, 0x11, 0x1f, 0xc0, 0x0a, 0x6f, 0x4a, 0xcc, 0xed, 0x80,
+	0x8d, 0x2e, 0x5d, 0x14, 0xb6, 0xdf, 0xe8, 0x19, 0x63, 0x62, 0xb4, 0xdf, 0x2e, 0x81, 0xbc, 0x52,
+	0x93, 0x50, 0xa8, 0x6c, 0x5b, 0x8f, 0xa9, 0x99, 0x3b, 0xd4, 0x76, 0x99, 0x71, 0x91, 0xf7, 0x76,
+	0x72, 0x0f, 0x33, 0x2f, 0x40, 0xc1, 0x9d, 0xbb, 0x0e, 0x45, 0xc4, 0x80, 0x6c, 0xbf, 0x1c, 0xae,
+	0xc3, 0x78, 0xe4, 0x81, 0x74, 0x1d, 0x8a, 0x22, 0x54, 0x32, 0x84, 0xa7, 0x92, 0x07, 0x8f, 0xe5,
+	0x0e, 0x90, 0x48, 0x04, 0xa1, 0x29, 0x4f, 0xa5, 0x2f, 0x92, 0x24, 0x4a, 0x19, 0xe4, 0x9b, 0xd0,
+	0xd0, 0x0d, 0xa3, 0xd7, 0xe9, 0xd9, 0xdc, 0xd2, 0x9d, 0x37, 0x95, 0xe0, 0x42, 0xc4, 0x4b, 0x8a,
+	0xe5, 0x1b, 0x9b, 0x58, 0x31, 0xc6, 0xe5, 0x35, 0xbf, 0xfe, 0xfd, 0x1f, 0x5d, 0x7b, 0xe6, 0x07,
+	0x3f, 0xba, 0xf6, 0xcc, 0x0f, 0x7f, 0x74, 0xed, 0x99, 0x9f, 0x3f, 0xbc, 0x56, 0xf8, 0xfe, 0xe1,
+	0xb5, 0xc2, 0x0f, 0x0e, 0xaf, 0x15, 0x7e, 0x78, 0x78, 0xad, 0xf0, 0xef, 0x0f, 0xaf, 0x15, 0xfe,
+	0xd2, 0x7f, 0xb8, 0xf6, 0xcc, 0xd7, 0x3e, 0x1f, 0x55, 0x67, 0x4e, 0x55, 0x67, 0x4e, 0x09, 0x9f,
+	0xeb, 0xee, 0xb6, 0xe7, 0x58, 0x75, 0xa2, 0x12, 0x55, 0x9d, 0xff, 0x1f, 0x00, 0x00, 0xff, 0xff,
+	0xcf, 0xb3, 0xea, 0x7d, 0xb7, 0xb2, 0x00, 0x00,
 }
 
 func (m *AbstractPodTemplate) Marshal() (dAtA []byte, err error) {
@@ -4010,6 +4045,18 @@ func (m *AbstractSink) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.Sqs != nil {
+		{
+			size, err := m.Sqs.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintGenerated(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x32
+	}
 	if m.Serve != nil {
 		{
 			size, err := m.Serve.MarshalToSizedBuffer(dAtA[:i])
@@ -4363,6 +4410,30 @@ func (m *Backoff) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	_ = i
 	var l int
 	_ = l
+	if m.Jitter != nil {
+		i -= 8
+		encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(*m.Jitter))))
+		i--
+		dAtA[i] = 0x29
+	}
+	if m.Cap != nil {
+		{
+			size, err := m.Cap.MarshalToSizedBuffer(dAtA[:i])
+			if err != nil {
+				return 0, err
+			}
+			i -= size
+			i = encodeVarintGenerated(dAtA, i, uint64(size))
+		}
+		i--
+		dAtA[i] = 0x22
+	}
+	if m.Factor != nil {
+		i -= 8
+		encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(*m.Factor))))
+		i--
+		dAtA[i] = 0x19
+	}
 	if m.Steps != nil {
 		i = encodeVarintGenerated(dAtA, i, uint64(*m.Steps))
 		i--
@@ -9587,6 +9658,44 @@ func (m *Source) MarshalToSizedBuffer(dAtA []byte) (int, error) {
 	return len(dAtA) - i, nil
 }
 
+func (m *SqsSink) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalToSizedBuffer(dAtA[:size])
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *SqsSink) MarshalTo(dAtA []byte) (int, error) {
+	size := m.Size()
+	return m.MarshalToSizedBuffer(dAtA[:size])
+}
+
+func (m *SqsSink) MarshalToSizedBuffer(dAtA []byte) (int, error) {
+	i := len(dAtA)
+	_ = i
+	var l int
+	_ = l
+	i -= len(m.QueueOwnerAWSAccountID)
+	copy(dAtA[i:], m.QueueOwnerAWSAccountID)
+	i = encodeVarintGenerated(dAtA, i, uint64(len(m.QueueOwnerAWSAccountID)))
+	i--
+	dAtA[i] = 0x1a
+	i -= len(m.QueueName)
+	copy(dAtA[i:], m.QueueName)
+	i = encodeVarintGenerated(dAtA, i, uint64(len(m.QueueName)))
+	i--
+	dAtA[i] = 0x12
+	i -= len(m.AWSRegion)
+	copy(dAtA[i:], m.AWSRegion)
+	i = encodeVarintGenerated(dAtA, i, uint64(len(m.AWSRegion)))
+	i--
+	dAtA[i] = 0xa
+	return len(dAtA) - i, nil
+}
+
 func (m *SqsSource) Marshal() (dAtA []byte, err error) {
 	size := m.Size()
 	dAtA = make([]byte, size)
@@ -10851,6 +10960,10 @@ func (m *AbstractSink) Size() (n int) {
 		l = m.Serve.Size()
 		n += 1 + l + sovGenerated(uint64(l))
 	}
+	if m.Sqs != nil {
+		l = m.Sqs.Size()
+		n += 1 + l + sovGenerated(uint64(l))
+	}
 	return n
 }
 
@@ -10965,6 +11078,16 @@ func (m *Backoff) Size() (n int) {
 	if m.Steps != nil {
 		n += 1 + sovGenerated(uint64(*m.Steps))
 	}
+	if m.Factor != nil {
+		n += 9
+	}
+	if m.Cap != nil {
+		l = m.Cap.Size()
+		n += 1 + l + sovGenerated(uint64(l))
+	}
+	if m.Jitter != nil {
+		n += 9
+	}
 	return n
 }
 
@@ -12874,6 +12997,21 @@ func (m *Source) Size() (n int) {
 	return n
 }
 
+func (m *SqsSink) Size() (n int) {
+	if m == nil {
+		return 0
+	}
+	var l int
+	_ = l
+	l = len(m.AWSRegion)
+	n += 1 + l + sovGenerated(uint64(l))
+	l = len(m.QueueName)
+	n += 1 + l + sovGenerated(uint64(l))
+	l = len(m.QueueOwnerAWSAccountID)
+	n += 1 + l + sovGenerated(uint64(l))
+	return n
+}
+
 func (m *SqsSource) Size() (n int) {
 	if m == nil {
 		return 0
@@ -13371,6 +13509,7 @@ func (this *AbstractSink) String() string {
 		`Blackhole:` + strings.Replace(this.Blackhole.String(), "Blackhole", "Blackhole", 1) + `,`,
 		`UDSink:` + strings.Replace(this.UDSink.String(), "UDSink", "UDSink", 1) + `,`,
 		`Serve:` + strings.Replace(this.Serve.String(), "ServeSink", "ServeSink", 1) + `,`,
+		`Sqs:` + strings.Replace(this.Sqs.String(), "SqsSink", "SqsSink", 1) + `,`,
 		`}`,
 	}, "")
 	return s
@@ -13442,6 +13581,9 @@ func (this *Backoff) String() string {
 	s := strings.Join([]string{`&Backoff{`,
 		`Interval:` + strings.Replace(fmt.Sprintf("%v", this.Interval), "Duration", "v11.Duration", 1) + `,`,
 		`Steps:` + valueToStringGenerated(this.Steps) + `,`,
+		`Factor:` + valueToStringGenerated(this.Factor) + `,`,
+		`Cap:` + strings.Replace(fmt.Sprintf("%v", this.Cap), "Duration", "v11.Duration", 1) + `,`,
+		`Jitter:` + valueToStringGenerated(this.Jitter) + `,`,
 		`}`,
 	}, "")
 	return s
@@ -14757,6 +14899,18 @@ func (this *Source) String() string {
 	}, "")
 	return s
 }
+func (this *SqsSink) String() string {
+	if this == nil {
+		return "nil"
+	}
+	s := strings.Join([]string{`&SqsSink{`,
+		`AWSRegion:` + fmt.Sprintf("%v", this.AWSRegion) + `,`,
+		`QueueName:` + fmt.Sprintf("%v", this.QueueName) + `,`,
+		`QueueOwnerAWSAccountID:` + fmt.Sprintf("%v", this.QueueOwnerAWSAccountID) + `,`,
+		`}`,
+	}, "")
+	return s
+}
 func (this *SqsSource) String() string {
 	if this == nil {
 		return "nil"
@@ -15863,6 +16017,42 @@ func (m *AbstractSink) Unmarshal(dAtA []byte) error {
 				return err
 			}
 			iNdEx = postIndex
+		case 6:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Sqs", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Sqs == nil {
+				m.Sqs = &SqsSink{}
+			}
+			if err := m.Sqs.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
 		default:
 			iNdEx = preIndex
 			skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -16728,6 +16918,66 @@ func (m *Backoff) Unmarshal(dAtA []byte) error {
 				}
 			}
 			m.Steps = &v
+		case 3:
+			if wireType != 1 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Factor", wireType)
+			}
+			var v uint64
+			if (iNdEx + 8) > l {
+				return io.ErrUnexpectedEOF
+			}
+			v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))
+			iNdEx += 8
+			v2 := float64(math.Float64frombits(v))
+			m.Factor = &v2
+		case 4:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Cap", wireType)
+			}
+			var msglen int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				msglen |= int(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			if msglen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + msglen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			if m.Cap == nil {
+				m.Cap = &v11.Duration{}
+			}
+			if err := m.Cap.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
+				return err
+			}
+			iNdEx = postIndex
+		case 5:
+			if wireType != 1 {
+				return fmt.Errorf("proto: wrong wireType = %d for field Jitter", wireType)
+			}
+			var v uint64
+			if (iNdEx + 8) > l {
+				return io.ErrUnexpectedEOF
+			}
+			v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:]))
+			iNdEx += 8
+			v2 := float64(math.Float64frombits(v))
+			m.Jitter = &v2
 		default:
 			iNdEx = preIndex
 			skippy, err := skipGenerated(dAtA[iNdEx:])
@@ -33258,6 +33508,152 @@ func (m *Source) Unmarshal(dAtA []byte) error {
 	}
 	return nil
 }
+func (m *SqsSink) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowGenerated
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= uint64(b&0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: SqsSink: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: SqsSink: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field AWSRegion", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.AWSRegion = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 2:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field QueueName", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.QueueName = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		case 3:
+			if wireType != 2 {
+				return fmt.Errorf("proto: wrong wireType = %d for field QueueOwnerAWSAccountID", wireType)
+			}
+			var stringLen uint64
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowGenerated
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				stringLen |= uint64(b&0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			intStringLen := int(stringLen)
+			if intStringLen < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			postIndex := iNdEx + intStringLen
+			if postIndex < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if postIndex > l {
+				return io.ErrUnexpectedEOF
+			}
+			m.QueueOwnerAWSAccountID = string(dAtA[iNdEx:postIndex])
+			iNdEx = postIndex
+		default:
+			iNdEx = preIndex
+			skippy, err := skipGenerated(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if (skippy < 0) || (iNdEx+skippy) < 0 {
+				return ErrInvalidLengthGenerated
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
 func (m *SqsSource) Unmarshal(dAtA []byte) error {
 	l := len(dAtA)
 	iNdEx := 0
diff --git a/pkg/apis/numaflow/v1alpha1/generated.proto b/pkg/apis/numaflow/v1alpha1/generated.proto
index 373bb2ea..f9b8ac15 100644
--- a/pkg/apis/numaflow/v1alpha1/generated.proto
+++ b/pkg/apis/numaflow/v1alpha1/generated.proto
@@ -146,6 +146,10 @@ message AbstractSink {
   // Serve sink is used to return results when working with a ServingPipeline.
   // +optional
   optional ServeSink serve = 5;
+
+  // SQS sink is used to write the data to the AWS SQS.
+  // +optional
+  optional SqsSink sqs = 6;
 }
 
 message AbstractVertex {
@@ -229,14 +233,32 @@ message Authorization {
 
 // Backoff defines parameters used to systematically configure the retry strategy.
 message Backoff {
-  // Interval sets the delay to wait before retry, after a failure occurs.
+  // Interval sets the initial retry duration, after a failure occurs.
   // +kubebuilder:default="1ms"
   // +optional
   optional .k8s.io.apimachinery.pkg.apis.meta.v1.Duration interval = 1;
 
-  // Steps defines the number of times to try writing to a sink including retries
+  // Steps defines the maximum number of retry attempts
   // +optional
   optional uint32 steps = 2;
+
+  // Interval is multiplied by factor each iteration, if factor is not zero
+  // and the limits imposed by Steps and Cap have not been reached.
+  // +optional
+  optional double factor = 3;
+
+  // A limit on revised values of the interval parameter. If a
+  // multiplication by the factor parameter would make the interval
+  // exceed the cap then the interval is set to the cap and the
+  // steps parameter is set to zero.
+  // +optional
+  optional .k8s.io.apimachinery.pkg.apis.meta.v1.Duration cap = 4;
+
+  // The sleep at each iteration is the interval plus an additional
+  // amount chosen uniformly at random from the interval between
+  // zero and `jitter*interval`.
+  // +optional
+  optional double jitter = 5;
 }
 
 // BasicAuth represents the basic authentication approach which contains a user name and a password.
@@ -1414,10 +1436,10 @@ message RedisSettings {
   optional string sentinel = 4;
 }
 
-// RetryStrategy struct encapsulates the settings for retrying operations in the event of failures.
-// It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.
+// The RetryStrategy struct defines the configuration for handling operation retries in case of failures.
+// It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.
 message RetryStrategy {
-  // BackOff specifies the parameters for the backoff strategy, controlling how delays between retries should increase.
+  // BackOff specifies the parameters for the exponential backoff strategy, controlling how delays between retries should increase.
   // +optional
   optional Backoff backoff = 1;
 
@@ -1748,6 +1770,17 @@ message Source {
   optional SqsSource sqs = 10;
 }
 
+message SqsSink {
+  // AWSRegion is the AWS Region where the SQS queue is located
+  optional string awsRegion = 1;
+
+  // QueueName is the name of the SQS queue
+  optional string queueName = 2;
+
+  // QueueOwnerAWSAccountID is the queue owner aws account id
+  optional string queueOwnerAWSAccountID = 3;
+}
+
 // SqsSource represents the configuration of an AWS SQS source
 message SqsSource {
   // AWSRegion is the AWS Region where the SQS queue is located
diff --git a/pkg/apis/numaflow/v1alpha1/retry_strategy.go b/pkg/apis/numaflow/v1alpha1/retry_strategy.go
index c21be28a..87d4e400 100644
--- a/pkg/apis/numaflow/v1alpha1/retry_strategy.go
+++ b/pkg/apis/numaflow/v1alpha1/retry_strategy.go
@@ -30,10 +30,10 @@ const (
 	OnFailureDrop     OnFailureRetryStrategy = "drop"     // Drop the operation and perform no further action.
 )
 
-// RetryStrategy struct encapsulates the settings for retrying operations in the event of failures.
-// It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.
+// The RetryStrategy struct defines the configuration for handling operation retries in case of failures.
+// It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.
 type RetryStrategy struct {
-	// BackOff specifies the parameters for the backoff strategy, controlling how delays between retries should increase.
+	// BackOff specifies the parameters for the exponential backoff strategy, controlling how delays between retries should increase.
 	// +optional
 	BackOff *Backoff `json:"backoff,omitempty" protobuf:"bytes,1,opt,name=backoff"`
 	// OnFailure specifies the action to take when the specified retry strategy fails.
@@ -49,34 +49,46 @@ type RetryStrategy struct {
 
 // Backoff defines parameters used to systematically configure the retry strategy.
 type Backoff struct {
-	// Interval sets the delay to wait before retry, after a failure occurs.
+	// Interval sets the initial retry duration, after a failure occurs.
 	// +kubebuilder:default="1ms"
 	// +optional
 	Interval *metav1.Duration `json:"interval,omitempty" protobuf:"bytes,1,opt,name=interval"`
-	// Steps defines the number of times to try writing to a sink including retries
+	// Steps defines the maximum number of retry attempts
 	// +optional
 	Steps *uint32 `json:"steps,omitempty" protobuf:"bytes,2,opt,name=steps"`
-	// TODO(Retry): Enable after we add support for exponential backoff
-	//// +optional
-	//Cap *metav1.Duration `json:"cap,omitempty" protobuf:"bytes,3,opt,name=cap"`
-	//// +optional
-	//Factor *floatstr `json:"factor,omitempty" protobuf:"bytes,2,opt,name=factor"`
-	//// +optional
-	//Jitter *floatstr `json:"jitter,omitempty" protobuf:"bytes,3,opt,name=jitter"`
+	// Interval is multiplied by factor each iteration, if factor is not zero
+	// and the limits imposed by Steps and Cap have not been reached.
+	// +optional
+	Factor *float64 `json:"factor,omitempty" protobuf:"bytes,3,opt,name=factor"`
+	// A limit on revised values of the interval parameter. If a
+	// multiplication by the factor parameter would make the interval
+	// exceed the cap then the interval is set to the cap and the
+	// steps parameter is set to zero.
+	// +optional
+	Cap *metav1.Duration `json:"cap,omitempty" protobuf:"bytes,4,opt,name=cap"`
+	// The sleep at each iteration is the interval plus an additional
+	// amount chosen uniformly at random from the interval between
+	// zero and `jitter*interval`.
+	// +optional
+	Jitter *float64 `json:"jitter,omitempty" protobuf:"bytes,5,opt,name=jitter"`
 }
 
 // GetBackoff constructs a wait.Backoff configuration using default values and optionally overrides
 // these defaults with custom settings specified in the RetryStrategy.
 func (r RetryStrategy) GetBackoff() wait.Backoff {
 	// Initialize the Backoff structure with default values.
+	// Default backoff strategy is indefinite number of
+	// retries with fixed interval of 1ms between retries.
 	wt := wait.Backoff{
 		Duration: DefaultRetryInterval,
 		Steps:    DefaultRetrySteps,
+		Factor:   DefaultFactor,
+		Cap:      DefaultMaxRetryInterval,
 	}
 
-	// If a custom back-off configuration is present, check and substitute the respective parts.
+	// If a custom exponential back-off configuration is present, check and substitute the respective parts.
 	if r.BackOff != nil {
-		// If a custom Interval is specified, override the default Duration.
+		// If a custom Interval is specified, override the default Interval.
 		if r.BackOff.Interval != nil {
 			wt.Duration = r.BackOff.Interval.Duration
 		}
@@ -84,6 +96,18 @@ func (r RetryStrategy) GetBackoff() wait.Backoff {
 		if r.BackOff.Steps != nil {
 			wt.Steps = int(*r.BackOff.Steps)
 		}
+		// If a custom Factor is specified, override the default Factor.
+		if r.BackOff.Factor != nil {
+			wt.Factor = *r.BackOff.Factor
+		}
+		// If a custom Cap is specified, set the Cap.
+		if r.BackOff.Cap != nil {
+			wt.Cap = r.BackOff.Cap.Duration
+		}
+		// If a custom Jitter is specified, set the Jitter.
+		if r.BackOff.Jitter != nil {
+			wt.Jitter = *r.BackOff.Jitter
+		}
 	}
 
 	// Returns the fully configured Backoff structure, which is either default or overridden by custom settings.
diff --git a/pkg/apis/numaflow/v1alpha1/sink.go b/pkg/apis/numaflow/v1alpha1/sink.go
index 0c9c72a1..ad4fbab5 100644
--- a/pkg/apis/numaflow/v1alpha1/sink.go
+++ b/pkg/apis/numaflow/v1alpha1/sink.go
@@ -49,6 +49,9 @@ type AbstractSink struct {
 	// Serve sink is used to return results when working with a ServingPipeline.
 	// +optional
 	Serve *ServeSink `json:"serve,omitempty" protobuf:"bytes,5,opt,name=serve"`
+	// SQS sink is used to write the data to the AWS SQS.
+	// +optional
+	Sqs *SqsSink `json:"sqs,omitempty" protobuf:"bytes,6,opt,name=sqs"`
 }
 
 func (s Sink) getContainers(req getContainerReq) ([]corev1.Container, []corev1.Container, error) {
diff --git a/pkg/apis/numaflow/v1alpha1/sqs_sink.go b/pkg/apis/numaflow/v1alpha1/sqs_sink.go
new file mode 100644
index 00000000..452fc12c
--- /dev/null
+++ b/pkg/apis/numaflow/v1alpha1/sqs_sink.go
@@ -0,0 +1,12 @@
+package v1alpha1
+
+type SqsSink struct {
+	// AWSRegion is the AWS Region where the SQS queue is located
+	AWSRegion string `json:"awsRegion" protobuf:"bytes,1,name=awsRegion"`
+
+	// QueueName is the name of the SQS queue
+	QueueName string `json:"queueName" protobuf:"bytes,2,name=queueName"`
+
+	// QueueOwnerAWSAccountID is the queue owner aws account id
+	QueueOwnerAWSAccountID string `json:"queueOwnerAWSAccountID" protobuf:"bytes,3,name=queueOwnerAWSAccountID"`
+}
diff --git a/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go
index 3fed1514..9ceab3a5 100644
--- a/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/numaflow/v1alpha1/zz_generated.deepcopy.go
@@ -133,6 +133,11 @@ func (in *AbstractSink) DeepCopyInto(out *AbstractSink) {
 		*out = new(ServeSink)
 		**out = **in
 	}
+	if in.Sqs != nil {
+		in, out := &in.Sqs, &out.Sqs
+		*out = new(SqsSink)
+		**out = **in
+	}
 	return
 }
 
@@ -286,6 +291,21 @@ func (in *Backoff) DeepCopyInto(out *Backoff) {
 		*out = new(uint32)
 		**out = **in
 	}
+	if in.Factor != nil {
+		in, out := &in.Factor, &out.Factor
+		*out = new(float64)
+		**out = **in
+	}
+	if in.Cap != nil {
+		in, out := &in.Cap, &out.Cap
+		*out = new(metav1.Duration)
+		**out = **in
+	}
+	if in.Jitter != nil {
+		in, out := &in.Jitter, &out.Jitter
+		*out = new(float64)
+		**out = **in
+	}
 	return
 }
 
@@ -2797,6 +2817,22 @@ func (in *Source) DeepCopy() *Source {
 	return out
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *SqsSink) DeepCopyInto(out *SqsSink) {
+	*out = *in
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new SqsSink.
+func (in *SqsSink) DeepCopy() *SqsSink {
+	if in == nil {
+		return nil
+	}
+	out := new(SqsSink)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *SqsSource) DeepCopyInto(out *SqsSource) {
 	*out = *in
diff --git a/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go b/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go
index b97c9229..98c52c40 100644
--- a/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go
+++ b/pkg/apis/numaflow/v1alpha1/zz_generated.openapi.go
@@ -117,6 +117,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
 		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Sink":                             schema_pkg_apis_numaflow_v1alpha1_Sink(ref),
 		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SlidingWindow":                    schema_pkg_apis_numaflow_v1alpha1_SlidingWindow(ref),
 		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Source":                           schema_pkg_apis_numaflow_v1alpha1_Source(ref),
+		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SqsSink":                          schema_pkg_apis_numaflow_v1alpha1_SqsSink(ref),
 		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SqsSource":                        schema_pkg_apis_numaflow_v1alpha1_SqsSource(ref),
 		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Status":                           schema_pkg_apis_numaflow_v1alpha1_Status(ref),
 		"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.TLS":                              schema_pkg_apis_numaflow_v1alpha1_TLS(ref),
@@ -330,11 +331,17 @@ func schema_pkg_apis_numaflow_v1alpha1_AbstractSink(ref common.ReferenceCallback
 							Ref:         ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServeSink"),
 						},
 					},
+					"sqs": {
+						SchemaProps: spec.SchemaProps{
+							Description: "SQS sink is used to write the data to the AWS SQS.",
+							Ref:         ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SqsSink"),
+						},
+					},
 				},
 			},
 		},
 		Dependencies: []string{
-			"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Blackhole", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.KafkaSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Log", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServeSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDSink"},
+			"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Blackhole", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.KafkaSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Log", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServeSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SqsSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDSink"},
 	}
 }
 
@@ -668,17 +675,37 @@ func schema_pkg_apis_numaflow_v1alpha1_Backoff(ref common.ReferenceCallback) com
 				Properties: map[string]spec.Schema{
 					"interval": {
 						SchemaProps: spec.SchemaProps{
-							Description: "Interval sets the delay to wait before retry, after a failure occurs.",
+							Description: "Interval sets the initial retry duration, after a failure occurs.",
 							Ref:         ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
 						},
 					},
 					"steps": {
 						SchemaProps: spec.SchemaProps{
-							Description: "Steps defines the number of times to try writing to a sink including retries",
+							Description: "Steps defines the maximum number of retry attempts",
 							Type:        []string{"integer"},
 							Format:      "int64",
 						},
 					},
+					"factor": {
+						SchemaProps: spec.SchemaProps{
+							Description: "Interval is multiplied by factor each iteration, if factor is not zero and the limits imposed by Steps and Cap have not been reached.",
+							Type:        []string{"number"},
+							Format:      "double",
+						},
+					},
+					"cap": {
+						SchemaProps: spec.SchemaProps{
+							Description: "A limit on revised values of the interval parameter. If a multiplication by the factor parameter would make the interval exceed the cap then the interval is set to the cap and the steps parameter is set to zero.",
+							Ref:         ref("k8s.io/apimachinery/pkg/apis/meta/v1.Duration"),
+						},
+					},
+					"jitter": {
+						SchemaProps: spec.SchemaProps{
+							Description: "The sleep at each iteration is the interval plus an additional amount chosen uniformly at random from the interval between zero and `jitter*interval`.",
+							Type:        []string{"number"},
+							Format:      "double",
+						},
+					},
 				},
 			},
 		},
@@ -4605,12 +4632,12 @@ func schema_pkg_apis_numaflow_v1alpha1_RetryStrategy(ref common.ReferenceCallbac
 	return common.OpenAPIDefinition{
 		Schema: spec.Schema{
 			SchemaProps: spec.SchemaProps{
-				Description: "RetryStrategy struct encapsulates the settings for retrying operations in the event of failures. It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.",
+				Description: "The RetryStrategy struct defines the configuration for handling operation retries in case of failures. It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.",
 				Type:        []string{"object"},
 				Properties: map[string]spec.Schema{
 					"backoff": {
 						SchemaProps: spec.SchemaProps{
-							Description: "BackOff specifies the parameters for the backoff strategy, controlling how delays between retries should increase.",
+							Description: "BackOff specifies the parameters for the exponential backoff strategy, controlling how delays between retries should increase.",
 							Ref:         ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Backoff"),
 						},
 					},
@@ -5583,6 +5610,12 @@ func schema_pkg_apis_numaflow_v1alpha1_Sink(ref common.ReferenceCallback) common
 							Ref:         ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServeSink"),
 						},
 					},
+					"sqs": {
+						SchemaProps: spec.SchemaProps{
+							Description: "SQS sink is used to write the data to the AWS SQS.",
+							Ref:         ref("github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SqsSink"),
+						},
+					},
 					"fallback": {
 						SchemaProps: spec.SchemaProps{
 							Description: "Fallback sink can be imagined as DLQ for primary Sink. The writes to Fallback sink will only be initiated if the ud-sink response field sets it.",
@@ -5600,7 +5633,7 @@ func schema_pkg_apis_numaflow_v1alpha1_Sink(ref common.ReferenceCallback) common
 			},
 		},
 		Dependencies: []string{
-			"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.AbstractSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Blackhole", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.KafkaSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Log", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RetryStrategy", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServeSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDSink"},
+			"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.AbstractSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Blackhole", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.KafkaSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.Log", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.RetryStrategy", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.ServeSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.SqsSink", "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1.UDSink"},
 	}
 }
 
@@ -5702,6 +5735,43 @@ func schema_pkg_apis_numaflow_v1alpha1_Source(ref common.ReferenceCallback) comm
 	}
 }
 
+func schema_pkg_apis_numaflow_v1alpha1_SqsSink(ref common.ReferenceCallback) common.OpenAPIDefinition {
+	return common.OpenAPIDefinition{
+		Schema: spec.Schema{
+			SchemaProps: spec.SchemaProps{
+				Type: []string{"object"},
+				Properties: map[string]spec.Schema{
+					"awsRegion": {
+						SchemaProps: spec.SchemaProps{
+							Description: "AWSRegion is the AWS Region where the SQS queue is located",
+							Default:     "",
+							Type:        []string{"string"},
+							Format:      "",
+						},
+					},
+					"queueName": {
+						SchemaProps: spec.SchemaProps{
+							Description: "QueueName is the name of the SQS queue",
+							Default:     "",
+							Type:        []string{"string"},
+							Format:      "",
+						},
+					},
+					"queueOwnerAWSAccountID": {
+						SchemaProps: spec.SchemaProps{
+							Description: "QueueOwnerAWSAccountID is the queue owner aws account id",
+							Default:     "",
+							Type:        []string{"string"},
+							Format:      "",
+						},
+					},
+				},
+				Required: []string{"awsRegion", "queueName", "queueOwnerAWSAccountID"},
+			},
+		},
+	}
+}
+
 func schema_pkg_apis_numaflow_v1alpha1_SqsSource(ref common.ReferenceCallback) common.OpenAPIDefinition {
 	return common.OpenAPIDefinition{
 		Schema: spec.Schema{
diff --git a/pkg/apis/proto/reduce/v1/reduce.proto b/pkg/apis/proto/reduce/v1/reduce.proto
index 2e209854..0264524f 100644
--- a/pkg/apis/proto/reduce/v1/reduce.proto
+++ b/pkg/apis/proto/reduce/v1/reduce.proto
@@ -33,9 +33,7 @@ service Reduce {
   rpc IsReady(google.protobuf.Empty) returns (ReadyResponse);
 }
 
-/**
- * ReduceRequest represents a request element.
- */
+// ReduceRequest represents a request element.
 message ReduceRequest {
   // WindowOperation represents a window operation.
   // For Aligned windows, OPEN, APPEND and CLOSE events are sent.
@@ -71,9 +69,8 @@ message Window {
   string slot = 3;
 }
 
-/**
- * ReduceResponse represents a response element.
- */
+
+// ReduceResponse represents a response element.
 message ReduceResponse {
   // Result represents a result element. It contains the result of the reduce function.
   message Result {
@@ -91,9 +88,7 @@ message ReduceResponse {
   bool EOF = 3;
 }
 
-/**
- * ReadyResponse is the health check result.
- */
+// ReadyResponse is the health check result.
 message ReadyResponse {
   bool ready = 1;
 }
diff --git a/pkg/apis/proto/reduce/wal/wal.proto b/pkg/apis/proto/reduce/wal/wal.proto
index 7b930f8e..6a135f45 100644
--- a/pkg/apis/proto/reduce/wal/wal.proto
+++ b/pkg/apis/proto/reduce/wal/wal.proto
@@ -14,6 +14,22 @@ message GCEvent {
   repeated string keys = 3;
 }
 
+// Window represents an aligned window.
+message Window {
+  // start_time of the window
+  google.protobuf.Timestamp start_time = 1;
+  // end time of the window
+  google.protobuf.Timestamp end_time = 2;
+}
+
+// WindowManagerState is the state of the window manager
+message WindowManagerState {
+  // list of active windows
+  repeated Window windows = 1;
+  // we use int64 for the end time because it will be -1 if there are no windows
+  int64 max_deleted_window_end_time = 2;
+}
+
 message Footer {
   // the latest event time in the current Segment
   google.protobuf.Timestamp latest_event_time = 1;
diff --git a/pkg/apis/proto/sessionreduce/v1/sessionreduce.proto b/pkg/apis/proto/sessionreduce/v1/sessionreduce.proto
index 994f4010..4694a512 100644
--- a/pkg/apis/proto/sessionreduce/v1/sessionreduce.proto
+++ b/pkg/apis/proto/sessionreduce/v1/sessionreduce.proto
@@ -42,9 +42,7 @@ message KeyedWindow {
   repeated string keys = 4;
 }
 
-/**
- * SessionReduceRequest represents a request element.
- */
+// SessionReduceRequest represents a request element.
 message SessionReduceRequest {
   // WindowOperation represents a window operation.
   // For Aligned window values can be one of OPEN, CLOSE, EXPAND, MERGE and APPEND.
@@ -74,9 +72,8 @@ message SessionReduceRequest {
   WindowOperation operation = 2;
 }
 
-/**
- * SessionReduceResponse represents a response element.
- */
+
+// SessionReduceResponse represents a response element.
 message SessionReduceResponse {
   // Result represents a result element. It contains the result of the reduce function.
   message Result {
@@ -94,9 +91,8 @@ message SessionReduceResponse {
   bool EOF = 3;
 }
 
-/**
- * ReadyResponse is the health check result.
- */
+
+// ReadyResponse is the health check result.
 message ReadyResponse {
   bool ready = 1;
 }
diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go
index ef0b55e4..41d1e2ec 100644
--- a/pkg/metrics/metrics.go
+++ b/pkg/metrics/metrics.go
@@ -283,7 +283,7 @@ var (
 		Buckets:   prometheus.ExponentialBucketsRange(1, 5000, 5),
 	}, []string{LabelPipeline, LabelVertex, LabelVertexReplicaIndex})
 
-	// ReduceProcessTime reduce ForwardTask processing latency
+	// ReduceProcessTime indicates the time it took to apply reduce UDF to a window
 	ReduceProcessTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
 		Subsystem: "reduce_pnf",
 		Name:      "process_time",
@@ -291,21 +291,14 @@ var (
 		Buckets:   prometheus.ExponentialBucketsRange(1, 1200000, 5),
 	}, []string{LabelVertex, LabelPipeline, LabelVertexReplicaIndex})
 
-	// ReduceForwardTime is used to indicate the time it took to forward the writeMessages
+	// ReduceForwardTime indicates the time it took to forward the readMessages from ISB to PBQ
 	ReduceForwardTime = promauto.NewHistogramVec(prometheus.HistogramOpts{
-		Subsystem: "reduce_pnf",
+		Subsystem: "reduce_data_forward",
 		Name:      "forward_time",
 		Help:      "Reduce forward time (1 to 100000 microseconds)",
 		Buckets:   prometheus.ExponentialBucketsRange(1, 100000, 5),
 	}, []string{LabelPipeline, LabelVertex, LabelVertexReplicaIndex})
 
-	// ReducePartitionsInFlight is used to indicate the partitions in flight
-	ReducePartitionsInFlight = promauto.NewGaugeVec(prometheus.GaugeOpts{
-		Subsystem: "reduce_pnf",
-		Name:      "partitions_inflight",
-		Help:      "Total number of partitions in flight",
-	}, []string{LabelVertex, LabelPipeline, LabelVertexReplicaIndex})
-
 	// ActiveWindowsCount is used to indicate the number of active windows
 	ActiveWindowsCount = promauto.NewGaugeVec(prometheus.GaugeOpts{
 		Subsystem: "reduce",
diff --git a/pkg/reconciler/cmd/start.go b/pkg/reconciler/cmd/start.go
index 33835c1a..2923cadb 100644
--- a/pkg/reconciler/cmd/start.go
+++ b/pkg/reconciler/cmd/start.go
@@ -20,6 +20,7 @@ import (
 	"context"
 	"fmt"
 	"os"
+	"regexp"
 	"time"
 
 	"github.com/go-logr/zapr"
@@ -132,9 +133,11 @@ func Start(namespaced bool, managedNamespace string) {
 	if svrVersion, err := kubeClient.ServerVersion(); err != nil {
 		logger.Fatalw("Failed to get k8s cluster server version", zap.Error(err))
 	} else {
-		k8sVersion := fmt.Sprintf("%s.%s", svrVersion.Major, svrVersion.Minor)
+		// Some k8s distro (e.g. v1.30.11-eks-bcf3d70) may have a suffix in the minor version, e.g. 30+
+		pattern := regexp.MustCompile(`[^0-9]`)
+		k8sVersion := fmt.Sprintf("%s.%s", svrVersion.Major, pattern.ReplaceAllString(svrVersion.Minor, ""))
 		os.Setenv(dfv1.EnvK8sServerVersion, k8sVersion)
-		logger.Infof("Kubernetes server version: %s", k8sVersion)
+		logger.Infof("Kubernetes server version: %s, distro: %s", k8sVersion, svrVersion.GitVersion)
 	}
 
 	// Readiness probe
diff --git a/pkg/reconciler/validator/pipeline_validate.go b/pkg/reconciler/validator/pipeline_validate.go
index 54803b57..603ef00a 100644
--- a/pkg/reconciler/validator/pipeline_validate.go
+++ b/pkg/reconciler/validator/pipeline_validate.go
@@ -22,6 +22,7 @@ import (
 	"k8s.io/apimachinery/pkg/util/intstr"
 	k8svalidation "k8s.io/apimachinery/pkg/util/validation"
 
+	"github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1"
 	dfv1 "github.com/numaproj/numaflow/pkg/apis/numaflow/v1alpha1"
 )
 
@@ -132,6 +133,14 @@ func ValidatePipeline(pl *dfv1.Pipeline) error {
 		} else {
 			toFromEdge[e.From+e.To] = true
 		}
+
+		if e.Conditions != nil {
+			if e.Conditions.Tags != nil {
+				if len(e.Conditions.Tags.Values) == 0 {
+					return fmt.Errorf("invalid edge: conditional forwarding requires at least one tag value")
+				}
+			}
+		}
 	}
 
 	if len(namesInEdges) != len(names) {
@@ -603,8 +612,8 @@ func validateSource(source dfv1.Source) error {
 // validateSink initiates the validation of the sink spec
 func validateSink(sink dfv1.Sink) error {
 	// check the sinks retry strategy validity.
-	if ok := hasValidSinkRetryStrategy(sink); !ok {
-		return fmt.Errorf("given OnFailure strategy is fallback but fallback sink is not provided")
+	if err := hasValidSinkRetryStrategy(sink); err != nil {
+		return err
 	}
 	// TODO: add more validations for each sink type
 	return nil
@@ -612,21 +621,49 @@ func validateSink(sink dfv1.Sink) error {
 
 // HasValidSinkRetryStrategy checks if the provided RetryStrategy is valid based on the sink's configuration.
 // This validation ensures that the retry strategy is compatible with the sink's current setup
-func hasValidSinkRetryStrategy(s dfv1.Sink) bool {
+func hasValidSinkRetryStrategy(s dfv1.Sink) error {
 	// If the OnFailure strategy is set to fallback, but no fallback sink is provided in the Sink struct,
 	// we return an error
 	if s.RetryStrategy.OnFailure != nil && *s.RetryStrategy.OnFailure == dfv1.OnFailureFallback && !hasValidFallbackSink(&s) {
-		return false
+		return fmt.Errorf("given OnFailure strategy is fallback but fallback sink is not provided")
 	}
-	// If steps are provided in the strategy they cannot be 0, as we do not allow no tries for writing
-	if s.RetryStrategy.BackOff != nil && s.RetryStrategy.BackOff.Steps != nil && *s.RetryStrategy.BackOff.Steps == 0 {
-		return false
+
+	if s.RetryStrategy.BackOff != nil {
+		// If steps are provided in the strategy they cannot be 0, as we do not allow no tries for writing
+		if s.RetryStrategy.BackOff.Steps != nil && *s.RetryStrategy.BackOff.Steps == 0 {
+			return fmt.Errorf("steps in backoff strategy cannot be 0")
+		}
+		// If factor is provided in the strategy it should be greater than or equal to 1
+		if s.RetryStrategy.BackOff.Factor != nil && *s.RetryStrategy.BackOff.Factor < 1 {
+			return fmt.Errorf("factor in backoff strategy cannot be less than 1")
+		}
+
+		// If cap and interval are provided, cap must be greater than or equal to interval
+		if s.RetryStrategy.BackOff.Cap != nil && s.RetryStrategy.BackOff.Interval != nil {
+			if s.RetryStrategy.BackOff.Cap.Duration < s.RetryStrategy.BackOff.Interval.Duration {
+				return fmt.Errorf("cap in backoff strategy cannot be less than interval")
+			}
+		}
+
+		// If cap is provided but interval isn't, cap must be greater than or equal to default interval value
+		if s.RetryStrategy.BackOff.Cap != nil && s.RetryStrategy.BackOff.Interval == nil {
+			if s.RetryStrategy.BackOff.Cap.Duration < v1alpha1.DefaultRetryInterval {
+				return fmt.Errorf("cap in backoff strategy cannot be less than default interval value, if interval is not provided")
+			}
+		}
+
+		// If jitter is provided, it should be greater than or equal to 0 and less than 1
+		// Jitter is typically used to introduce small random variations to avoid synchronized retries
+		// A jitter value less than 1 ensures that the delay remains within a reasonable range around the base delay.
+		if s.RetryStrategy.BackOff.Jitter != nil && (*s.RetryStrategy.BackOff.Jitter < 0 || *s.RetryStrategy.BackOff.Jitter >= 1) {
+			return fmt.Errorf("jitter in backoff strategy should be between 0 and 1")
+		}
 	}
-	// If no errors are found, the function returns true indicating the validation passed.
-	return true
+	// If no errors are found, the function returns nil indicating the validation passed.
+	return nil
 }
 
 // HasValidFallbackSink checks if the Sink vertex has a valid fallback sink configured
 func hasValidFallbackSink(s *dfv1.Sink) bool {
-	return s.Fallback != nil && s.Fallback.UDSink != nil
+	return s.Fallback != nil && s.Fallback.IsAnySinkSpecified()
 }
diff --git a/pkg/reduce/data_forward.go b/pkg/reduce/data_forward.go
index 1a96facc..4060d0f4 100644
--- a/pkg/reduce/data_forward.go
+++ b/pkg/reduce/data_forward.go
@@ -284,11 +284,22 @@ func (df *DataForward) replayForAlignedWindows(ctx context.Context, discoveredWA
 // and writes the windowRequests to pbq
 func (df *DataForward) forwardAChunk(ctx context.Context) {
 	// Initialize metric labels
-	metricLabelsWithPartition := map[string]string{metrics.LabelVertex: df.vertexName, metrics.LabelPipeline: df.pipelineName, metrics.LabelVertexType: string(dfv1.VertexTypeReduceUDF), metrics.LabelVertexReplicaIndex: strconv.Itoa(int(df.vertexReplica)), metrics.LabelPartitionName: df.fromBufferPartition.GetName()}
+	metricLabels := map[string]string{
+		metrics.LabelVertex:             df.vertexName,
+		metrics.LabelPipeline:           df.pipelineName,
+		metrics.LabelVertexReplicaIndex: strconv.Itoa(int(df.vertexReplica)),
+	}
+	metricLabelsWithPartition := map[string]string{
+		metrics.LabelVertex:             df.vertexName,
+		metrics.LabelPipeline:           df.pipelineName,
+		metrics.LabelVertexType:         string(dfv1.VertexTypeReduceUDF),
+		metrics.LabelVertexReplicaIndex: strconv.Itoa(int(df.vertexReplica)),
+		metrics.LabelPartitionName:      df.fromBufferPartition.GetName(),
+	}
 
+	start := time.Now()
+	readStart := time.Now()
 	readMessages, err := df.fromBufferPartition.Read(ctx, df.opts.readBatchSize)
-	totalBytes := 0
-	dataBytes := 0
 	if err != nil {
 		df.log.Errorw("Failed to read from isb", zap.Error(err))
 		metrics.ReadMessagesError.With(metricLabelsWithPartition).Inc()
@@ -359,6 +370,8 @@ func (df *DataForward) forwardAChunk(ctx context.Context) {
 		df.currentWatermark = time.Time(processorWM)
 	}
 
+	totalBytes := 0
+	dataBytes := 0
 	for _, m := range readMessages {
 		if !df.keyed {
 			m.Keys = []string{dfv1.DefaultKeyForNonKeyedData}
@@ -371,12 +384,14 @@ func (df *DataForward) forwardAChunk(ctx context.Context) {
 		}
 	}
 
+	metrics.ReadProcessingTime.With(metricLabelsWithPartition).Observe(float64(time.Since(readStart).Microseconds()))
 	metrics.ReadBytesCount.With(metricLabelsWithPartition).Add(float64(totalBytes))
-
 	metrics.ReadDataBytesCount.With(metricLabelsWithPartition).Add(float64(dataBytes))
 
 	// readMessages has to be written to PBQ, acked, etc.
 	df.process(ctx, readMessages)
+
+	metrics.ReduceForwardTime.With(metricLabels).Observe(float64(time.Since(start).Microseconds()))
 }
 
 // associatePBQAndPnF associates a PBQ with the partition if a PBQ exists, else creates a new one and then associates
@@ -460,6 +475,7 @@ func (df *DataForward) process(ctx context.Context, messages []*isb.ReadMessage)
 		df.log.Errorw("Failed to write messages", zap.Int("totalMessages", len(messages)), zap.Int("writtenMessage", len(successfullyWrittenMessages)))
 	}
 
+	ackStart := time.Now()
 	// ack the control messages
 	if len(ctrlMessages) != 0 {
 		df.ackMessages(ctx, ctrlMessages)
@@ -479,6 +495,7 @@ func (df *DataForward) process(ctx context.Context, messages []*isb.ReadMessage)
 
 	// ack successful messages
 	df.ackMessages(ctx, successfullyWrittenMessages)
+	metrics.AckProcessingTime.With(metricLabelsWithPartition).Observe(float64(time.Since(ackStart).Microseconds()))
 
 	// close any windows that need to be closed.
 	// since the watermark will be same for all the messages in the batch
diff --git a/pkg/reduce/pnf/pnf.go b/pkg/reduce/pnf/pnf.go
index cc2f782f..32daaa6b 100644
--- a/pkg/reduce/pnf/pnf.go
+++ b/pkg/reduce/pnf/pnf.go
@@ -142,6 +142,13 @@ func (pf *ProcessAndForward) AsyncSchedulePnF(ctx context.Context, partitionID *
 // invokeUDF reads requests from the supplied PBQ, invokes the UDF to gets the response and writes the response to the
 // main channel.
 func (pf *ProcessAndForward) invokeUDF(ctx context.Context, done chan struct{}, pid *partition.ID, pbqReader pbq.Reader) {
+	metricLabels := map[string]string{
+		metrics.LabelVertex:             pf.vertexName,
+		metrics.LabelPipeline:           pf.pipelineName,
+		metrics.LabelVertexReplicaIndex: strconv.Itoa(int(pf.vertexReplica)),
+	}
+	start := time.Now()
+
 	defer func() {
 		pf.mu.Lock()
 		delete(pf.pnfRoutines, pid.String())
@@ -174,6 +181,8 @@ outerLoop:
 			pf.responseCh <- response
 		}
 	}
+
+	metrics.ReduceProcessTime.With(metricLabels).Observe(float64(time.Since(start).Microseconds()))
 }
 
 // forwardResponses forwards the writeMessages to the ISBs. It also publishes the watermark and invokes GC on PBQ.
@@ -431,6 +440,7 @@ func (pf *ProcessAndForward) writeToBuffer(ctx context.Context, edgeName string,
 		metrics.LabelPartitionName:      pf.toBuffers[edgeName][partition].GetName(),
 	}
 
+	writeStart := time.Now()
 	writeMessages := resultMessages
 
 	// write to isb with infinite exponential backoff (until shutdown is triggered)
@@ -486,6 +496,7 @@ func (pf *ProcessAndForward) writeToBuffer(ctx context.Context, edgeName string,
 		return nil, ctxClosedErr
 	}
 
+	metrics.WriteProcessingTime.With(metricLabelsWithPartition).Observe(float64(time.Since(writeStart).Microseconds()))
 	metrics.WriteMessagesCount.With(metricLabelsWithPartition).Add(float64(writeCount))
 	metrics.WriteBytesCount.With(metricLabelsWithPartition).Add(writeBytes)
 	return offsets, nil
diff --git a/pkg/sinks/forward/forward.go b/pkg/sinks/forward/forward.go
index 15b14daf..ca961973 100644
--- a/pkg/sinks/forward/forward.go
+++ b/pkg/sinks/forward/forward.go
@@ -372,7 +372,7 @@ func (df *DataForward) ackFromBuffer(ctx context.Context, offsets []isb.Offset)
 	return ctxClosedErr
 }
 
-// writeToSink forwards an array of messages to a sink and it is a blocking call it keeps retrying
+// writeToSink forwards an array of messages to a sink, and it is a blocking call it keeps retrying
 // until shutdown has been initiated. The function also evaluates whether to use a fallback sink based
 // on the error and configuration.
 func (df *DataForward) writeToSink(ctx context.Context, sinkWriter sinker.SinkWriter, messagesToTry []isb.Message, isFbSinkWriter bool) ([]isb.Offset, []isb.Message, error) {
@@ -462,9 +462,10 @@ func (df *DataForward) writeToSink(ctx context.Context, sinkWriter sinker.SinkWr
 			return true, nil
 		})
 
-		if err != nil {
+		if ok, _ := df.IsShuttingDown(); err != nil && ok {
 			return nil, nil, err
 		}
+
 		// Check what actions are required once the writing loop is completed
 		// Break if no further action is required
 		if !df.handlePostRetryFailures(&messagesToTry, failStrategy, &fallbackMessages, sinkWriter) {
@@ -484,10 +485,8 @@ func (df *DataForward) handlePostRetryFailures(messagesToTry *[]isb.Message, fai
 
 	// Check if we still have messages left to be processed
 	if len(*messagesToTry) > 0 {
-
 		df.opts.logger.Infof("Retries exhausted in sink, messagesLeft %d, Next strategy %s",
 			len(*messagesToTry), failStrategy)
-
 		// Check what is the failure strategy to be followed after retry exhaustion
 		switch failStrategy {
 		case dfv1.OnFailureRetry:
@@ -578,6 +577,8 @@ func (df *DataForward) getBackOffConditions(isFallbackSink bool) (wait.Backoff,
 		return wait.Backoff{
 			Duration: dfv1.DefaultRetryInterval,
 			Steps:    dfv1.DefaultRetrySteps,
+			Factor:   dfv1.DefaultFactor,
+			Cap:      dfv1.DefaultMaxRetryInterval,
 		}, dfv1.OnFailureRetry
 	}
 	// Initial interval duration and number of retries are taken from DataForward settings.
diff --git a/rust/Cargo.lock b/rust/Cargo.lock
index 65f96792..da95c05d 100644
--- a/rust/Cargo.lock
+++ b/rust/Cargo.lock
@@ -1007,6 +1007,18 @@ version = "1.0.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04"
 
+[[package]]
+name = "duct"
+version = "0.13.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e4ab5718d1224b63252cd0c6f74f6480f9ffeb117438a2e0f5cf6d9a4798929c"
+dependencies = [
+ "libc",
+ "once_cell",
+ "os_pipe",
+ "shared_child",
+]
+
 [[package]]
 name = "dunce"
 version = "1.0.5"
@@ -1577,16 +1589,18 @@ dependencies = [
 
 [[package]]
 name = "hyper-util"
-version = "0.1.10"
+version = "0.1.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4"
+checksum = "b1c293b6b3d21eca78250dc7dbebd6b9210ec5530e038cbfe0661b5c47ab06e8"
 dependencies = [
  "bytes",
  "futures-channel",
+ "futures-core",
  "futures-util",
  "http 1.3.1",
  "http-body 1.0.1",
  "hyper 1.6.0",
+ "libc",
  "pin-project-lite",
  "socket2",
  "tokio",
@@ -1857,6 +1871,15 @@ dependencies = [
  "serde_json",
 ]
 
+[[package]]
+name = "krb5-src"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a04b2f0dfefd7b54af22d22768dc3ab1ed5e55e6d5ff6af3f619069aa17c0ba4"
+dependencies = [
+ "duct",
+]
+
 [[package]]
 name = "kube"
 version = "0.99.0"
@@ -2120,6 +2143,27 @@ dependencies = [
  "autocfg",
 ]
 
+[[package]]
+name = "num_enum"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179"
+dependencies = [
+ "num_enum_derive",
+]
+
+[[package]]
+name = "num_enum_derive"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56"
+dependencies = [
+ "proc-macro-crate",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.100",
+]
+
 [[package]]
 name = "numaflow"
 version = "0.1.0"
@@ -2176,6 +2220,7 @@ dependencies = [
  "kube",
  "numaflow 0.2.1",
  "numaflow-jetstream",
+ "numaflow-kafka",
  "numaflow-models",
  "numaflow-monitor",
  "numaflow-pb",
@@ -2184,7 +2229,7 @@ dependencies = [
  "parking_lot",
  "pep440_rs",
  "pin-project",
- "prometheus-client 0.22.3",
+ "prometheus-client",
  "prost 0.13.5",
  "prost-types",
  "pulsar",
@@ -2206,6 +2251,25 @@ dependencies = [
  "trait-variant",
 ]
 
+[[package]]
+name = "numaflow-http"
+version = "0.1.0"
+dependencies = [
+ "axum",
+ "bytes",
+ "chrono",
+ "hyper 1.6.0",
+ "hyper-util",
+ "serde",
+ "serde_json",
+ "thiserror 2.0.12",
+ "tokio",
+ "tokio-stream",
+ "tower",
+ "tracing",
+ "uuid",
+]
+
 [[package]]
 name = "numaflow-jetstream"
 version = "0.1.0"
@@ -2222,6 +2286,19 @@ dependencies = [
  "tracing",
 ]
 
+[[package]]
+name = "numaflow-kafka"
+version = "0.1.0"
+dependencies = [
+ "bytes",
+ "futures",
+ "rdkafka",
+ "thiserror 2.0.12",
+ "tokio",
+ "tracing",
+ "uuid",
+]
+
 [[package]]
 name = "numaflow-models"
 version = "0.0.0-pre"
@@ -2320,6 +2397,28 @@ version = "0.1.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
 
+[[package]]
+name = "openssl-src"
+version = "300.5.0+3.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8ce546f549326b0e6052b649198487d91320875da901e7bd11a06d1ee3f9c2f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "openssl-sys"
+version = "0.9.108"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847"
+dependencies = [
+ "cc",
+ "libc",
+ "openssl-src",
+ "pkg-config",
+ "vcpkg",
+]
+
 [[package]]
 name = "ordered-float"
 version = "2.10.1"
@@ -2329,6 +2428,16 @@ dependencies = [
  "num-traits",
 ]
 
+[[package]]
+name = "os_pipe"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "outref"
 version = "0.5.2"
@@ -2391,11 +2500,12 @@ dependencies = [
 
 [[package]]
 name = "pep440_rs"
-version = "0.6.6"
+version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "466eada3179c2e069ca897b99006cbb33f816290eaeec62464eea907e22ae385"
+checksum = "31095ca1f396e3de32745f42b20deef7bc09077f918b085307e8eab6ddd8fb9c"
 dependencies = [
  "once_cell",
+ "serde",
  "unicode-width",
  "unscanny",
 ]
@@ -2503,6 +2613,12 @@ dependencies = [
  "spki",
 ]
 
+[[package]]
+name = "pkg-config"
+version = "0.3.32"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
+
 [[package]]
 name = "portable-atomic"
 version = "1.11.0"
@@ -2535,24 +2651,21 @@ dependencies = [
 ]
 
 [[package]]
-name = "proc-macro2"
-version = "1.0.94"
+name = "proc-macro-crate"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+checksum = "edce586971a4dfaa28950c6f18ed55e0406c1ab88bbce2c6f6293a7aaba73d35"
 dependencies = [
- "unicode-ident",
+ "toml_edit",
 ]
 
 [[package]]
-name = "prometheus-client"
-version = "0.22.3"
+name = "proc-macro2"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
 dependencies = [
- "dtoa",
- "itoa",
- "parking_lot",
- "prometheus-client-derive-encode",
+ "unicode-ident",
 ]
 
 [[package]]
@@ -2710,7 +2823,7 @@ checksum = "b820744eb4dc9b57a3398183639c511b5a26d2ed702cedd3febaa1393caa22cc"
 dependencies = [
  "bytes",
  "getrandom 0.3.2",
- "rand 0.9.0",
+ "rand 0.9.1",
  "ring",
  "rustc-hash 2.1.1",
  "rustls 0.23.25",
@@ -2764,13 +2877,12 @@ dependencies = [
 
 [[package]]
 name = "rand"
-version = "0.9.0"
+version = "0.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94"
+checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97"
 dependencies = [
  "rand_chacha 0.9.0",
  "rand_core 0.9.3",
- "zerocopy",
 ]
 
 [[package]]
@@ -2824,6 +2936,36 @@ dependencies = [
  "yasna",
 ]
 
+[[package]]
+name = "rdkafka"
+version = "0.37.0"
+source = "git+https://github.com/numaproj-labs/rust-rdkafka.git?rev=b5c79588583b6ae5446d0d4ba99f7c6ee709420e#b5c79588583b6ae5446d0d4ba99f7c6ee709420e"
+dependencies = [
+ "futures-channel",
+ "futures-util",
+ "libc",
+ "log",
+ "rdkafka-sys",
+ "serde",
+ "serde_derive",
+ "serde_json",
+ "slab",
+ "tokio",
+]
+
+[[package]]
+name = "rdkafka-sys"
+version = "4.8.0+2.3.0"
+source = "git+https://github.com/numaproj-labs/rust-rdkafka.git?rev=b5c79588583b6ae5446d0d4ba99f7c6ee709420e#b5c79588583b6ae5446d0d4ba99f7c6ee709420e"
+dependencies = [
+ "cmake",
+ "libc",
+ "num_enum",
+ "openssl-sys",
+ "pkg-config",
+ "sasl2-sys",
+]
+
 [[package]]
 name = "redox_syscall"
 version = "0.5.10"
@@ -3174,6 +3316,19 @@ version = "1.0.20"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
 
+[[package]]
+name = "sasl2-sys"
+version = "0.1.22+2.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05f2a7f7efd9fc98b3a9033272df10709f5ee3fa0eabbd61a527a3a1ed6bd3c6"
+dependencies = [
+ "cc",
+ "duct",
+ "krb5-src",
+ "libc",
+ "pkg-config",
+]
+
 [[package]]
 name = "schannel"
 version = "0.1.27"
@@ -3365,7 +3520,7 @@ dependencies = [
  "numaflow-models",
  "numaflow-pb",
  "parking_lot",
- "prometheus-client 0.23.1",
+ "prometheus-client",
  "rcgen",
  "reqwest 0.12.15",
  "rustls 0.23.25",
@@ -3415,6 +3570,16 @@ dependencies = [
  "lazy_static",
 ]
 
+[[package]]
+name = "shared_child"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e297bd52991bbe0686c086957bee142f13df85d1e79b0b21630a99d374ae9dc"
+dependencies = [
+ "libc",
+ "windows-sys 0.59.0",
+]
+
 [[package]]
 name = "shlex"
 version = "1.3.0"
@@ -3469,9 +3634,9 @@ checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
 
 [[package]]
 name = "socket2"
-version = "0.5.8"
+version = "0.5.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
+checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678"
 dependencies = [
  "libc",
  "windows-sys 0.52.0",
@@ -3711,9 +3876,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.44.2"
+version = "1.45.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
+checksum = "75ef51a33ef1da925cea3e4eb122833cb377c61439ca401b770f54902b806779"
 dependencies = [
  "backtrace",
  "bytes",
@@ -3802,6 +3967,23 @@ dependencies = [
  "webpki-roots 0.26.8",
 ]
 
+[[package]]
+name = "toml_datetime"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3da5db5a963e24bc68be8b17b6fa82814bb22ee8660f192bb182771d498f09a3"
+
+[[package]]
+name = "toml_edit"
+version = "0.22.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e"
+dependencies = [
+ "indexmap",
+ "toml_datetime",
+ "winnow",
+]
+
 [[package]]
 name = "tonic"
 version = "0.13.0"
@@ -4011,9 +4193,9 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
 
 [[package]]
 name = "unicode-width"
-version = "0.1.14"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
 
 [[package]]
 name = "unsafe-libyaml"
@@ -4075,7 +4257,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9"
 dependencies = [
  "getrandom 0.3.2",
- "rand 0.9.0",
+ "rand 0.9.1",
  "serde",
 ]
 
@@ -4085,6 +4267,12 @@ version = "0.1.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
 
+[[package]]
+name = "vcpkg"
+version = "0.2.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
+
 [[package]]
 name = "version_check"
 version = "0.9.5"
@@ -4530,6 +4718,15 @@ version = "0.53.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486"
 
+[[package]]
+name = "winnow"
+version = "0.7.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c06928c8748d81b05c9be96aad92e1b6ff01833332f281e8cfca3be4b35fc9ec"
+dependencies = [
+ "memchr",
+]
+
 [[package]]
 name = "winreg"
 version = "0.50.0"
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 24a8362c..c4287727 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -2,15 +2,19 @@
 resolver = "2"
 members = [
     "backoff",
-    "numaflow-models",
     "serving",
     "numaflow-core",
-    "numaflow-pb",
+    "numaflow",
+    "numaflow-monitor",
+    # extensions
     "extns/numaflow-pulsar",
     "extns/numaflow-sqs",
     "extns/numaflow-jetstream",
-    "numaflow",
-    "numaflow-monitor",
+    "extns/numaflow-kafka",
+    "extns/numaflow-http",
+    # generated
+    "numaflow-pb",
+    "numaflow-models",
 ]
 
 [workspace.lints.rust]
@@ -62,8 +66,10 @@ backoff = { path = "backoff" }
 numaflow-pb = { path = "numaflow-pb" }
 numaflow-pulsar = { path = "extns/numaflow-pulsar" }
 numaflow-jetstream = { path = "extns/numaflow-jetstream" }
+numaflow-kafka = { path = "extns/numaflow-kafka" }
 numaflow-sqs = { path = "extns/numaflow-sqs" }
 tokio = { version = "1.44.2", features = ["macros", "rt-multi-thread"] }
+tokio-stream = "0.1.17"
 bytes = "1.9.0"
 tracing = "0.1.41"
 async-nats = "0.40.0"
@@ -86,3 +92,5 @@ reqwest = { version = "0.12.12", default-features = false, features = [
     "rustls-tls",
     "json",
 ] }
+uuid = "1.16.0"
+futures = "0.3.31"
diff --git a/rust/build.sh b/rust/build.sh
index a0f93ee6..e9d80d9f 100644
--- a/rust/build.sh
+++ b/rust/build.sh
@@ -10,15 +10,20 @@ set -xeuo pipefail
 # Detect the host machine architecture
 ARCH=$(uname -m)
 
-apt update && apt install -y curl protobuf-compiler build-essential
+# Initialize the build environment
+initialize() {
+    apt update && apt install -y curl protobuf-compiler build-essential cmake
+
+    if [ ! -f "$HOME/.cargo/env" ]; then
+        curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
+    fi
+    . "$HOME/.cargo/env"
+}
 
-if [ ! -f "$HOME/.cargo/env" ]; then
-    curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
-fi
-. "$HOME/.cargo/env"
 
 # Function to build for aarch64
 build_aarch64() {
+    initialize
     apt install -y gcc-aarch64-linux-gnu
     sed -i "/^targets = \[/d" rust-toolchain.toml
     RUSTFLAGS='-C target-feature=+crt-static -C linker=aarch64-linux-gnu-gcc' cargo build --release --target aarch64-unknown-linux-gnu
@@ -27,6 +32,7 @@ build_aarch64() {
 
 # Function to build for x86_64
 build_x86_64() {
+    initialize
     apt install -y gcc-x86-64-linux-gnu
     sed -i "/^targets = \[/d" rust-toolchain.toml
     echo "targets = ['x86_64-unknown-linux-gnu']" >> rust-toolchain.toml
@@ -34,5 +40,26 @@ build_x86_64() {
     sed -i "/targets = \['x86_64-unknown-linux-gnu'\]/d" rust-toolchain.toml
 }
 
-build_aarch64
-build_x86_64
+# Check if an architecture argument was provided
+if [ $# -eq 0 ]; then
+    echo "Usage: $0 [aarch64|amd64|all]"
+    exit 1
+fi
+
+# Build based on the provided argument
+case "$1" in
+    "aarch64")
+        build_aarch64
+        ;;
+    "amd64")
+        build_x86_64
+        ;;
+    "all")
+        build_aarch64
+        build_x86_64
+        ;;
+    *)
+        echo "Invalid architecture. Use aarch64, amd64, or all"
+        exit 1
+        ;;
+esac
diff --git a/rust/extns/numaflow-http/Cargo.toml b/rust/extns/numaflow-http/Cargo.toml
new file mode 100644
index 00000000..a66cadce
--- /dev/null
+++ b/rust/extns/numaflow-http/Cargo.toml
@@ -0,0 +1,24 @@
+[package]
+name = "numaflow-http"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+tokio.workspace = true
+tokio-stream.workspace = true
+axum.workspace = true
+bytes.workspace = true
+thiserror = "2.0.12"
+serde.workspace = true
+tracing.workspace = true
+uuid = { workspace = true, features = ["v7"] }
+chrono = { version = "0.4", features = ["serde"] }
+serde_json = "1.0"
+
+[dev-dependencies]
+hyper = { version = "1.6.0", features = ["client", "http1"] }
+hyper-util = { version = "0.1.13", features = ["client", "client-legacy", "http1"] }
+tower.workspace = true
+
+[lints]
+workspace = true
diff --git a/rust/extns/numaflow-http/src/lib.rs b/rust/extns/numaflow-http/src/lib.rs
new file mode 100644
index 00000000..3de258c7
--- /dev/null
+++ b/rust/extns/numaflow-http/src/lib.rs
@@ -0,0 +1,529 @@
+//! HTTP source for Numaflow.
+//! There are two endpoints, one for health (`/health`) and another for data (`/data`).
+//! The `/vertices/` endpoint is a `POST` endpoint that accepts all content-types of data. The
+//! headers are propagated as is to the next vertex in the pipeline.
+//! `X-Numaflow-Id` header is added to the message to track the message across the pipeline.
+//! `X-Numaflow-Event-Time` is added to the message to track the event time of the message.
+
+use axum::{
+    Router,
+    extract::State,
+    http::{HeaderMap, StatusCode},
+    response::IntoResponse,
+    routing::{get, post},
+};
+use bytes::Bytes;
+use chrono::{DateTime, Utc};
+use serde::Serialize;
+use std::collections::HashMap;
+use std::net::SocketAddr;
+use std::time::Duration;
+use tokio::sync::{mpsc, oneshot};
+use tracing::{debug, error, info, trace, warn};
+use uuid::Uuid;
+
+/// HTTP source that manages incoming HTTP requests and forwards them to a processing channel
+pub struct HttpSource {
+    server_rx: mpsc::Receiver<HttpMessage>,
+    server_handle: tokio::task::JoinHandle<Result<()>>,
+    timeout: Duration,
+}
+
+/// Error types for the HTTP source
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("Channel send error: {0}")]
+    ChannelSend(String),
+    #[error("Channel Full: 429")]
+    ChannelFull(),
+    #[error("Server error: {0}")]
+    Server(String),
+}
+
+type Result<T> = std::result::Result<T, Error>;
+
+/// HTTP message containing the request body and headers
+#[derive(Debug, Clone)]
+pub struct HttpMessage {
+    pub body: Bytes,
+    pub headers: HashMap<String, String>,
+    pub event_time: DateTime<Utc>,
+    pub id: String,
+}
+
+/// Response for successful data ingestion
+#[derive(Serialize)]
+struct DataResponse {
+    message: String,
+    id: String,
+}
+
+/// HTTPSource Builder with custom buffer size
+pub struct HttpSourceBuilder {
+    /// Default buffer size is 2000
+    buffer_size: usize,
+    addr: SocketAddr,
+    timeout: Duration,
+}
+
+impl HttpSourceBuilder {
+    /// Create a new HttpSourceBuilder
+    pub fn new() -> Self {
+        Self {
+            buffer_size: 500,
+            addr: "0.0.0.0:8080".parse().expect("Invalid address"),
+            timeout: Duration::from_secs(1),
+        }
+    }
+
+    /// Set the buffer size for the channel
+    pub fn with_buffer_size(mut self, buffer_size: usize) -> Self {
+        self.buffer_size = buffer_size;
+        self
+    }
+
+    /// Set the buffer size for the channel
+    pub fn with_addr(mut self, addr: SocketAddr) -> Self {
+        self.addr = addr;
+        self
+    }
+
+    /// Build the HttpSource instance
+    pub fn build(self) -> HttpSource {
+        let (tx, rx) = mpsc::channel(self.buffer_size); // Increased buffer size for better throughput
+
+        let server_handle = tokio::spawn(start_server(tx, self.addr));
+
+        HttpSource {
+            server_rx: rx,
+            timeout: self.timeout,
+            server_handle,
+        }
+    }
+}
+
+impl Default for HttpSourceBuilder {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+#[derive(Debug)]
+pub enum HttpSourceMessage {
+    Read {
+        size: usize,
+        response_tx: oneshot::Sender<Result<Vec<HttpMessage>>>,
+    },
+    Ack {
+        offsets: Vec<String>,
+        response_tx: oneshot::Sender<Result<()>>,
+    },
+    Pending(oneshot::Sender<Option<usize>>),
+}
+
+impl HttpSource {
+    /// Background task that processes messages from the channel
+    async fn run(mut self, mut actor_rx: mpsc::Receiver<HttpSourceMessage>) -> Result<()> {
+        info!("HttpSource processor started");
+
+        while let Some(msg) = actor_rx.recv().await {
+            match msg {
+                HttpSourceMessage::Read { size, response_tx } => {
+                    let messages = self.read(size).await;
+                    debug!("Reading messages from HttpSource");
+                    response_tx.send(messages).expect("rx should be open");
+                }
+                HttpSourceMessage::Ack {
+                    offsets,
+                    response_tx,
+                } => {
+                    debug!(count = offsets.len(), "Ack'ing messages from HttpSource");
+                    response_tx
+                        .send(self.ack(offsets).await)
+                        .expect("rx should be open");
+                }
+                HttpSourceMessage::Pending(response_tx) => {
+                    let pending = self.pending().await;
+                    debug!(?pending, "Pending messages from HttpSource");
+                    response_tx.send(pending).expect("rx should be open");
+                }
+            }
+        }
+
+        info!("HttpSource processor stopped");
+        Ok(())
+    }
+
+    async fn pending(&self) -> Option<usize> {
+        Some(self.server_rx.len())
+    }
+
+    async fn read(&mut self, count: usize) -> Result<Vec<HttpMessage>> {
+        // return all the messages in self.server_rx as long as timeout is not reached and not
+        // exceeding the count.
+
+        let mut messages = vec![];
+
+        let timeout = tokio::time::timeout(self.timeout, std::future::pending::<()>());
+        tokio::pin!(timeout);
+
+        loop {
+            tokio::select! {
+                biased;
+
+                _ =  &mut timeout => {
+                    return Ok(messages);
+                }
+
+                message = self.server_rx.recv() => {
+                    // stream ended
+                    let Some(message) = message else {
+                        return Ok(messages);
+                    };
+                    messages.push(message);
+                }
+            }
+
+            if messages.len() >= count {
+                return Ok(messages);
+            }
+        }
+    }
+
+    /// http source cannot implement ack, we have already returned 202 or 429 accordingly
+    async fn ack(&self, _offsets: Vec<String>) -> Result<()> {
+        Ok(())
+    }
+}
+
+/// Send a message to the processing channel
+pub async fn send_message(tx: mpsc::Sender<HttpMessage>, message: HttpMessage) -> Result<()> {
+    match tx.try_send(message) {
+        Ok(_) => Ok(()),
+        Err(e) => match e {
+            mpsc::error::TrySendError::Full(_) => Err(Error::ChannelFull()),
+            mpsc::error::TrySendError::Closed(_) => {
+                Err(Error::ChannelSend("Channel is closed".to_string()))
+            }
+        },
+    }
+}
+
+/// Create an Axum router with the HTTP source endpoints
+pub fn create_router(tx: mpsc::Sender<HttpMessage>) -> Router {
+    Router::new()
+        .route("/health", get(health_handler))
+        // FIXME: should be "/vertices/"+vertexInstance.Vertex.Spec.Name
+        .route("/vertices", post(data_handler))
+        .with_state(tx)
+}
+
+/// Start the HTTP server on the specified address
+pub async fn start_server(tx: mpsc::Sender<HttpMessage>, addr: SocketAddr) -> Result<()> {
+    let router = create_router(tx);
+
+    info!(?addr, "Starting HTTP source server");
+
+    let listener = tokio::net::TcpListener::bind(addr)
+        .await
+        .map_err(|e| Error::Server(format!("Failed to bind to {}: {}", addr, e)))?;
+
+    axum::serve(listener, router)
+        .await
+        .map_err(|e| Error::Server(format!("Server error: {}", e)))?;
+
+    Ok(())
+}
+
+/// Health check endpoint handler
+async fn health_handler() -> impl IntoResponse {
+    StatusCode::OK
+}
+
+/// Data ingestion endpoint handler
+async fn data_handler(
+    State(http_source): State<mpsc::Sender<HttpMessage>>,
+    headers: HeaderMap,
+    body: Bytes,
+) -> impl IntoResponse {
+    // Convert headers to HashMap and ensure required headers are present
+    let mut header_map = HashMap::new();
+
+    for (key, value) in headers.iter() {
+        if let Ok(value_str) = value.to_str() {
+            header_map.insert(key.to_string(), value_str.to_string());
+        } else {
+            warn!(?key, "Skipping header with invalid UTF-8");
+        }
+    }
+
+    // Generate or extract X-Numaflow-Id
+    let id = header_map
+        .get("x-numaflow-id")
+        .cloned()
+        .unwrap_or_else(|| Uuid::now_v7().to_string());
+
+    // Ensure X-Numaflow-Id is in the headers
+    header_map.insert("X-Numaflow-Id".to_string(), id.clone());
+
+    // Generate or extract X-Numaflow-Event-Time
+    let event_time = header_map
+        .get("x-numaflow-event-time")
+        .and_then(|time_str| DateTime::parse_from_rfc3339(time_str).ok())
+        .map(|dt| dt.with_timezone(&Utc))
+        .unwrap_or_else(Utc::now);
+
+    // Ensure X-Numaflow-Event-Time is in the headers
+    header_map.insert("X-Numaflow-Event-Time".to_string(), event_time.to_rfc2822());
+
+    // Create the HTTP message
+    let message = HttpMessage {
+        body,
+        headers: header_map,
+        event_time,
+        id: id.clone(),
+    };
+
+    // Send the message to the processing channel
+    match send_message(http_source, message).await {
+        Ok(()) => {
+            trace!(?id, "Successfully queued message");
+            (
+                StatusCode::OK,
+                axum::Json(DataResponse {
+                    message: "Data received successfully".to_string(),
+                    id,
+                }),
+            )
+                .into_response()
+        }
+        Err(e) => match e {
+            Error::ChannelFull() => {
+                warn!(?e, "Buffer is full");
+                (
+                    StatusCode::TOO_MANY_REQUESTS,
+                    axum::Json(serde_json::json!({
+                        "error": "Pipeline has stalled, buffer is full",
+                        "details": e.to_string()
+                    })),
+                )
+                    .into_response()
+            }
+            e => {
+                error!(?e, "Failed to queue message");
+                (
+                    StatusCode::INTERNAL_SERVER_ERROR,
+                    axum::Json(serde_json::json!({
+                        "error": "Failed to process request",
+                        "details": e.to_string()
+                    })),
+                )
+                    .into_response()
+            }
+        },
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use axum::body::Body;
+    use hyper::{Method, Request, StatusCode};
+    use hyper_util::client::legacy::Client;
+    use std::net::TcpListener;
+    use std::time::Duration;
+    use tokio::sync::{mpsc, oneshot};
+    use tower::ServiceExt;
+
+    #[tokio::test]
+    async fn test_health_endpoint() {
+        let (tx, _rx) = mpsc::channel(500);
+
+        let app = create_router(tx);
+
+        let request = Request::builder()
+            .method(Method::GET)
+            .uri("/health")
+            .body(Body::empty())
+            .unwrap();
+
+        let response = app.oneshot(request).await.unwrap();
+        assert_eq!(response.status(), StatusCode::OK);
+    }
+
+    #[tokio::test]
+    async fn test_data_endpoint() {
+        let (tx, rx) = mpsc::channel(10);
+
+        let app = create_router(tx);
+
+        let request = Request::builder()
+            .method(Method::POST)
+            .uri("/vertices")
+            .header("Content-Type", "application/json")
+            .body(Body::from(r#"{"test": "data"}"#))
+            .unwrap();
+
+        let response = app.oneshot(request).await.unwrap();
+        assert_eq!(response.status(), StatusCode::OK);
+
+        assert_eq!(rx.len(), 1);
+    }
+
+    #[tokio::test]
+    async fn test_data_endpoint_with_custom_id() {
+        let (tx, rx) = mpsc::channel(10);
+
+        let app = create_router(tx);
+
+        let custom_id = "custom-test-id";
+        let request = Request::builder()
+            .method(Method::POST)
+            .uri("/vertices")
+            .header("Content-Type", "text/plain")
+            .header("X-Numaflow-Id", custom_id)
+            .body(Body::from("test data"))
+            .unwrap();
+
+        let response = app.oneshot(request).await.unwrap();
+        assert_eq!(response.status(), StatusCode::OK);
+
+        assert_eq!(rx.len(), 1);
+    }
+
+    #[tokio::test]
+    async fn test_http_source_read_with_real_server() {
+        // Bind to a random available port
+        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+        let addr = listener.local_addr().unwrap();
+        drop(listener); // Release the listener so HttpSource can bind to it
+
+        // Create HttpSource with the address
+        let http_source = HttpSourceBuilder::new()
+            .with_addr(addr)
+            .with_buffer_size(100)
+            .build();
+
+        // Create actor channel for communicating with HttpSource
+        let (actor_tx, actor_rx) = mpsc::channel::<HttpSourceMessage>(10);
+
+        // Spawn the HttpSource actor
+        let source_handle = tokio::spawn(async move { http_source.run(actor_rx).await });
+
+        // Wait a bit for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        // Create HTTP client
+        let client = Client::builder(hyper_util::rt::TokioExecutor::new()).build_http();
+
+        // Send multiple HTTP requests to the server
+        let test_data = vec![
+            (r#"{"message": "test1"}"#, "application/json"),
+            (r#"{"message": "test2"}"#, "application/json"),
+            ("plain text data", "text/plain"),
+        ];
+
+        for (body_data, content_type) in &test_data {
+            let request = Request::builder()
+                .method(Method::POST)
+                .uri(format!("http://{}/vertices", addr))
+                .header("Content-Type", *content_type)
+                .header("X-Numaflow-Id", format!("test-id-{}", uuid::Uuid::now_v7()))
+                .body((*body_data).to_string())
+                .unwrap();
+
+            let response = client.request(request).await.unwrap();
+            assert_eq!(response.status(), StatusCode::OK);
+        }
+
+        // Use the actor to read messages from HttpSource
+        let (read_tx, read_rx) = oneshot::channel();
+        actor_tx
+            .send(HttpSourceMessage::Read {
+                size: 3, // Request up to 3 messages (else it will wait for timeout)
+                response_tx: read_tx,
+            })
+            .await
+            .unwrap();
+
+        // Wait for the read response
+        let messages = read_rx.await.unwrap().unwrap();
+
+        // Verify we got the expected number of messages
+        assert_eq!(messages.len(), 3);
+
+        // Verify message contents
+        for message in messages.iter() {
+            assert!(!message.id.is_empty());
+            assert!(message.headers.contains_key("X-Numaflow-Id"));
+            assert!(message.headers.contains_key("X-Numaflow-Event-Time"));
+            assert!(message.headers.contains_key("content-type"));
+
+            // Check that the body matches what we sent
+            let body_str = String::from_utf8(message.body.to_vec()).unwrap();
+            assert!(test_data.iter().any(|(data, _)| *data == body_str));
+        }
+
+        // Test pending count
+        let (pending_tx, pending_rx) = oneshot::channel();
+        actor_tx
+            .send(HttpSourceMessage::Pending(pending_tx))
+            .await
+            .unwrap();
+
+        let pending_count = pending_rx.await.unwrap();
+        assert_eq!(pending_count, Some(0)); // Should be 0 since we read all messages
+
+        // Test ack (should always succeed for HTTP source)
+        let offsets: Vec<String> = messages.iter().map(|m| m.id.clone()).collect();
+        let (ack_tx, ack_rx) = oneshot::channel();
+        actor_tx
+            .send(HttpSourceMessage::Ack {
+                offsets,
+                response_tx: ack_tx,
+            })
+            .await
+            .unwrap();
+
+        let ack_result = ack_rx.await.unwrap();
+        assert!(ack_result.is_ok());
+
+        // Clean up
+        drop(actor_tx);
+        let _ = source_handle.await;
+    }
+
+    #[tokio::test]
+    async fn test_http_source_timeout_behavior() {
+        // Test that read method respects timeout when no messages are available
+        let listener = TcpListener::bind("127.0.0.1:0").unwrap();
+        let addr = listener.local_addr().unwrap();
+        drop(listener);
+
+        let http_source = HttpSourceBuilder::new()
+            .with_addr(addr)
+            .with_buffer_size(100)
+            .build();
+
+        let (actor_tx, actor_rx) = mpsc::channel::<HttpSourceMessage>(10);
+
+        let source_handle = tokio::spawn(async move { http_source.run(actor_rx).await });
+
+        // Try to read messages when none are available - should timeout and return empty vec
+        let (read_tx, read_rx) = oneshot::channel();
+        actor_tx
+            .send(HttpSourceMessage::Read {
+                size: 5,
+                response_tx: read_tx,
+            })
+            .await
+            .unwrap();
+
+        let messages = read_rx.await.unwrap().unwrap();
+        assert_eq!(messages.len(), 0); // Should be empty due to timeout
+
+        // Clean up
+        drop(actor_tx);
+        let _ = source_handle.await;
+    }
+}
diff --git a/rust/extns/numaflow-kafka/Cargo.toml b/rust/extns/numaflow-kafka/Cargo.toml
new file mode 100644
index 00000000..6777fdde
--- /dev/null
+++ b/rust/extns/numaflow-kafka/Cargo.toml
@@ -0,0 +1,21 @@
+[package]
+name = "numaflow-kafka"
+version = "0.1.0"
+edition = "2024"
+
+[dependencies]
+# TODO: fix when https://github.com/fede1024/rust-rdkafka/pull/756 is resolved
+rdkafka = { git = "https://github.com/numaproj-labs/rust-rdkafka.git", rev = "b5c79588583b6ae5446d0d4ba99f7c6ee709420e", default-features = false, features = ["cmake-build", "tokio", "ssl-vendored", "gssapi-vendored", "sasl"] }
+tokio = { workspace = true, features = ["sync"] }
+tracing.workspace = true
+thiserror.workspace = true
+bytes.workspace = true
+futures.workspace = true
+uuid = { workspace = true, features = ["v4"], optional = true }
+
+[features]
+kafka-tests = ["uuid"]
+kafka-tests-utils = ["uuid"]
+
+[lints]
+workspace = true
diff --git a/rust/extns/numaflow-kafka/src/lib.rs b/rust/extns/numaflow-kafka/src/lib.rs
new file mode 100644
index 00000000..38505d57
--- /dev/null
+++ b/rust/extns/numaflow-kafka/src/lib.rs
@@ -0,0 +1,365 @@
+use rdkafka::config::ClientConfig;
+use tracing::{error, warn};
+
+pub mod sink;
+pub mod source;
+
+const KAFKA_TOPIC_HEADER_KEY: &str = "X-NF-Kafka-TopicName";
+
+pub type Result<T> = core::result::Result<T, Error>;
+
+#[derive(thiserror::Error, Debug)]
+pub enum Error {
+    #[error("Connecting to Kafka {server} - {error}")]
+    Connection { server: String, error: String },
+
+    #[error("Kafka - {0}")]
+    Kafka(String),
+
+    #[error("{0}")]
+    Other(String),
+}
+
+/// Represents the SASL authentication mechanism for connecting to Kafka.
+///
+/// See the [Kafka Security documentation](https://kafka.apache.org/documentation/#security_sasl) for more details on each mechanism.
+#[derive(Debug, Clone, PartialEq)]
+pub enum KafkaSaslAuth {
+    /// SASL/PLAIN authentication mechanism.
+    ///
+    /// See: <https://kafka.apache.org/documentation/#security_sasl_plain>
+    ///
+    /// Requires a username and password.
+    Plain { username: String, password: String },
+    /// SASL/SCRAM-SHA-256 authentication mechanism.
+    ///
+    /// See: <https://kafka.apache.org/documentation/#security_sasl_scram>
+    ///
+    /// Requires a username and password.
+    ScramSha256 { username: String, password: String },
+    /// SASL/SCRAM-SHA-512 authentication mechanism.
+    ///
+    /// See: <https://kafka.apache.org/documentation/#security_sasl_scram>
+    ///
+    /// Requires a username and password.
+    ScramSha512 { username: String, password: String },
+    /// SASL/GSSAPI (Kerberos) authentication mechanism.
+    ///
+    /// See: <https://kafka.apache.org/documentation/#security_sasl_gssapi>
+    ///
+    /// Requires Kerberos configuration and credentials.
+    Gssapi {
+        /// Kerberos service name (e.g., "kafka").
+        service_name: String,
+        /// Kerberos realm (optional, may be required by some setups).
+        realm: String,
+        /// Kerberos principal (username).
+        username: String,
+        /// Kerberos password (optional, if not using keytab).
+        password: Option<String>,
+        /// Path to the keytab file (optional).
+        keytab: Option<String>,
+        /// Path to the Kerberos configuration file (optional).
+        kerberos_config: Option<String>,
+        /// Authentication type (e.g., "kinit").
+        auth_type: String,
+    },
+    /// SASL/OAUTHBEARER authentication mechanism.
+    ///
+    /// See: <https://kafka.apache.org/documentation/#security_sasl_oauthbearer>
+    ///
+    /// Requires OAuth2 client credentials and token endpoint.
+    Oauth {
+        /// OAuth2 client ID.
+        client_id: String,
+        /// OAuth2 client secret.
+        client_secret: String,
+        /// OAuth2 token endpoint URL.
+        token_endpoint: String,
+    },
+}
+
+#[derive(Debug, Clone, PartialEq)]
+/// Configuration for TLS settings when connecting to Kafka brokers.
+pub struct TlsConfig {
+    /// Whether to skip server certificate verification.
+    ///
+    /// When set to `true`, the client will not verify the server's certificate.
+    pub insecure_skip_verify: bool,
+
+    /// CA certificate used to verify the server's certificate.
+    /// Note: This will contain the actual certificate, not the path to the certificate.
+    pub ca_cert: Option<String>,
+
+    /// Client authentication certificates for mutual TLS (mTLS).
+    ///
+    /// If `Some`, client authentication will be enabled using the provided certificates.
+    /// If `None`, client authentication will be disabled.
+    pub client_auth: Option<TlsClientAuthCerts>,
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct TlsClientAuthCerts {
+    pub client_cert: String,
+    pub client_cert_private_key: String,
+}
+
+fn update_auth_config(
+    client_config: &mut ClientConfig,
+    tls_config: Option<TlsConfig>,
+    auth_config: Option<KafkaSaslAuth>,
+) {
+    let tls_enabled = tls_config.is_some();
+    if let Some(tls_config) = tls_config {
+        client_config.set("security.protocol", "SSL");
+        if tls_config.insecure_skip_verify {
+            warn!(
+                "'insecureSkipVerify' is set to true, certificate validation will not be performed when connecting to Kafka server"
+            );
+            client_config.set("enable.ssl.certificate.verification", "false");
+        }
+        if let Some(ca_cert) = tls_config.ca_cert {
+            client_config.set("ssl.ca.pem", ca_cert);
+        }
+        if let Some(client_auth) = tls_config.client_auth {
+            client_config
+                .set("ssl.certificate.pem", client_auth.client_cert)
+                .set("ssl.key.pem", client_auth.client_cert_private_key);
+        }
+    }
+
+    if let Some(auth) = auth_config {
+        client_config.set(
+            "security.protocol",
+            if tls_enabled {
+                "SASL_SSL"
+            } else {
+                "SASL_PLAINTEXT"
+            },
+        );
+        match auth {
+            KafkaSaslAuth::Plain { username, password } => {
+                client_config
+                    .set("sasl.mechanisms", "PLAIN")
+                    .set("sasl.username", username)
+                    .set("sasl.password", password);
+            }
+            KafkaSaslAuth::ScramSha256 { username, password } => {
+                client_config
+                    .set("sasl.mechanisms", "SCRAM-SHA-256")
+                    .set("sasl.username", username)
+                    .set("sasl.password", password);
+            }
+            KafkaSaslAuth::ScramSha512 { username, password } => {
+                client_config
+                    .set("sasl.mechanisms", "SCRAM-SHA-512")
+                    .set("sasl.username", username)
+                    .set("sasl.password", password);
+            }
+            KafkaSaslAuth::Gssapi {
+                service_name,
+                realm: _,
+                username,
+                password: _,
+                keytab,
+                kerberos_config,
+                auth_type: _,
+            } => {
+                client_config.set("sasl.mechanisms", "GSSAPI");
+                client_config.set("sasl.kerberos.service.name", service_name);
+                client_config.set("sasl.kerberos.principal", username);
+                if let Some(keytab) = keytab {
+                    client_config.set("sasl.kerberos.keytab", keytab);
+                }
+                if let Some(kerberos_config) = kerberos_config {
+                    client_config.set("sasl.kerberos.kinit.cmd", kerberos_config);
+                }
+            }
+            KafkaSaslAuth::Oauth {
+                client_id,
+                client_secret,
+                token_endpoint,
+            } => {
+                client_config.set("sasl.mechanisms", "OAUTHBEARER");
+                client_config.set("sasl.oauthbearer.client.id", client_id);
+                client_config.set("sasl.oauthbearer.client.secret", client_secret);
+                client_config.set("sasl.oauthbearer.token.endpoint.url", token_endpoint);
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use rdkafka::config::ClientConfig;
+    use std::collections::HashMap;
+
+    #[test]
+    fn test_update_auth_config_none() {
+        let mut config = ClientConfig::new();
+        update_auth_config(&mut config, None, None);
+        // Should not set any security or SASL keys
+        assert!(config.get("security.protocol").is_none());
+        assert!(config.get("sasl.mechanisms").is_none());
+    }
+
+    #[test]
+    fn test_update_auth_config_tls_only() {
+        let mut config = ClientConfig::new();
+        let tls = TlsConfig {
+            insecure_skip_verify: true,
+            ca_cert: Some("CA_CERT_DATA".to_string()),
+            client_auth: Some(TlsClientAuthCerts {
+                client_cert: "CLIENT_CERT_DATA".to_string(),
+                client_cert_private_key: "CLIENT_KEY_DATA".to_string(),
+            }),
+        };
+        update_auth_config(&mut config, Some(tls), None);
+        let expected_config = [
+            ("security.protocol", "SSL"),
+            ("enable.ssl.certificate.verification", "false"),
+            ("ssl.ca.pem", "CA_CERT_DATA"),
+            ("ssl.certificate.pem", "CLIENT_CERT_DATA"),
+            ("ssl.key.pem", "CLIENT_KEY_DATA"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+
+    #[test]
+    fn update_auth_config_with_plain_auth() {
+        let mut config = ClientConfig::new();
+        let auth = KafkaSaslAuth::Plain {
+            username: "user".to_string(),
+            password: "pass".to_string(),
+        };
+        update_auth_config(&mut config, None, Some(auth));
+        let expected_config = [
+            ("security.protocol", "SASL_PLAINTEXT"),
+            ("sasl.mechanisms", "PLAIN"),
+            ("sasl.username", "user"),
+            ("sasl.password", "pass"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+
+    #[test]
+    fn update_auth_config_with_scram_sha256() {
+        let mut config = ClientConfig::new();
+        let auth = KafkaSaslAuth::ScramSha256 {
+            username: "user256".to_string(),
+            password: "pass256".to_string(),
+        };
+        update_auth_config(&mut config, None, Some(auth));
+        let expected_config = [
+            ("security.protocol", "SASL_PLAINTEXT"),
+            ("sasl.mechanisms", "SCRAM-SHA-256"),
+            ("sasl.username", "user256"),
+            ("sasl.password", "pass256"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+
+    #[test]
+    fn update_auth_config_with_scram_sha512() {
+        let mut config = ClientConfig::new();
+        let auth = KafkaSaslAuth::ScramSha512 {
+            username: "user512".to_string(),
+            password: "pass512".to_string(),
+        };
+        update_auth_config(&mut config, None, Some(auth));
+        let expected_config = [
+            ("security.protocol", "SASL_PLAINTEXT"),
+            ("sasl.mechanisms", "SCRAM-SHA-512"),
+            ("sasl.username", "user512"),
+            ("sasl.password", "pass512"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+
+    #[test]
+    fn update_auth_config_with_gssapi() {
+        let mut config = ClientConfig::new();
+        let auth = KafkaSaslAuth::Gssapi {
+            service_name: "kafka".to_string(),
+            realm: "REALM".to_string(),
+            username: "principal".to_string(),
+            password: None,
+            keytab: Some("/path/to/keytab".to_string()),
+            kerberos_config: Some("/path/to/krb5.conf".to_string()),
+            auth_type: "kinit".to_string(),
+        };
+        update_auth_config(&mut config, None, Some(auth));
+        let expected_config = [
+            ("security.protocol", "SASL_PLAINTEXT"),
+            ("sasl.mechanisms", "GSSAPI"),
+            ("sasl.kerberos.service.name", "kafka"),
+            ("sasl.kerberos.principal", "principal"),
+            ("sasl.kerberos.keytab", "/path/to/keytab"),
+            ("sasl.kerberos.kinit.cmd", "/path/to/krb5.conf"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+
+    #[test]
+    fn update_auth_config_with_oauth() {
+        let mut config = ClientConfig::new();
+        let auth = KafkaSaslAuth::Oauth {
+            client_id: "cid".to_string(),
+            client_secret: "csecret".to_string(),
+            token_endpoint: "https://token".to_string(),
+        };
+        update_auth_config(&mut config, None, Some(auth));
+        let expected_config = [
+            ("security.protocol", "SASL_PLAINTEXT"),
+            ("sasl.mechanisms", "OAUTHBEARER"),
+            ("sasl.oauthbearer.client.id", "cid"),
+            ("sasl.oauthbearer.client.secret", "csecret"),
+            ("sasl.oauthbearer.token.endpoint.url", "https://token"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+
+    #[test]
+    fn update_auth_config_with_tls_and_plain() {
+        let mut config = ClientConfig::new();
+        let tls = TlsConfig {
+            insecure_skip_verify: false,
+            ca_cert: None,
+            client_auth: None,
+        };
+        let auth = KafkaSaslAuth::Plain {
+            username: "user".to_string(),
+            password: "pass".to_string(),
+        };
+        update_auth_config(&mut config, Some(tls), Some(auth));
+        let expected_config = [
+            ("security.protocol", "SASL_SSL"),
+            ("sasl.mechanisms", "PLAIN"),
+            ("sasl.username", "user"),
+            ("sasl.password", "pass"),
+        ]
+        .into_iter()
+        .map(|(k, v)| (k.to_string(), v.to_string()))
+        .collect::<HashMap<String, String>>();
+        assert_eq!(config.config_map().clone(), expected_config);
+    }
+}
diff --git a/rust/extns/numaflow-kafka/src/sink.rs b/rust/extns/numaflow-kafka/src/sink.rs
new file mode 100644
index 00000000..d962cd16
--- /dev/null
+++ b/rust/extns/numaflow-kafka/src/sink.rs
@@ -0,0 +1,303 @@
+use std::{collections::HashMap, time::Duration};
+
+use bytes::Bytes;
+use futures::{StreamExt, stream::FuturesUnordered};
+use rdkafka::{
+    ClientConfig,
+    config::RDKafkaLogLevel,
+    message::{Header, OwnedHeaders},
+    producer::{FutureProducer, FutureRecord},
+};
+
+use crate::{KafkaSaslAuth, TlsConfig};
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct KafkaSinkConfig {
+    /// The Kafka brokers to connect to.
+    pub brokers: Vec<String>,
+    /// The Kafka topic to send messages to.
+    pub topic: String,
+    /// The authentication mechanism to use for the Kafka consumer.
+    pub auth: Option<KafkaSaslAuth>,
+    /// The TLS configuration for the Kafka consumer.
+    pub tls: Option<TlsConfig>,
+    /// Whether to set the partition key when sending the messages to Kafka.
+    /// Whether to set the partition key when sending messages to Kafka.
+    /// When set to true, messages will be routed to specific partitions based on their partition_key.
+    /// The partition key is constructed using the list of keys associated with a message.
+    /// This ensures that messages with the same keys always go to the same partition,
+    /// which is useful for maintaining message ordering for related messages.
+    /// When set to false, messages will be distributed across partitions randomly.
+    pub set_partition_key: bool,
+}
+
+/// The Kafka sink client.
+pub struct KafkaSink {
+    topic: String,
+    producer: FutureProducer,
+    set_partition_key: bool,
+}
+
+/// The result of a sink operation corresponding to a [KafkaSinkMessage] with same id.
+pub struct KafkaSinkResponse {
+    /// ID of the message that was sent
+    pub id: String,
+    /// Status of the send operation
+    pub status: crate::Result<()>,
+}
+
+/// The message to send to Kafka. Input to [KafkaSink::sink_messages].
+pub struct KafkaSinkMessage {
+    /// ID of the message
+    pub id: String,
+    /// The partition key for the message. If not provided, the message will be sent to a random partition.
+    /// This is only used if [KafkaSinkConfig::set_partition_key] is true.
+    pub partition_key: Option<String>,
+    /// The headers to send with the message.
+    pub headers: HashMap<String, String>,
+    /// The message payload
+    pub payload: Bytes,
+}
+
+/// Create a new Kafka sink client.
+pub fn new_sink(config: KafkaSinkConfig) -> crate::Result<KafkaSink> {
+    let mut client_config = ClientConfig::new();
+    client_config
+        .set("bootstrap.servers", config.brokers.join(","))
+        .set("message.timeout.ms", "5000")
+        .set("client.id", "numaflow-kafka-sink")
+        .set_log_level(RDKafkaLogLevel::Warning);
+
+    crate::update_auth_config(&mut client_config, config.tls, config.auth);
+
+    let producer: FutureProducer = client_config
+        .create()
+        .map_err(|e| crate::Error::Kafka(format!("Failed to create producer: {}", e)))?;
+
+    Ok(KafkaSink {
+        producer,
+        topic: config.topic,
+        set_partition_key: config.set_partition_key,
+    })
+}
+
+impl KafkaSink {
+    /// Send the messages to Kafka.
+    pub async fn sink_messages(
+        &mut self,
+        messages: Vec<KafkaSinkMessage>,
+    ) -> crate::Result<Vec<KafkaSinkResponse>> {
+        // Create futures for all send operations and concurrently await on them
+        let mut send_futures = FuturesUnordered::new();
+        let message_count = messages.len();
+        for msg in messages {
+            let fut = async {
+                let KafkaSinkMessage {
+                    id,
+                    partition_key,
+                    headers: inp_headers,
+                    payload,
+                } = msg;
+                let mut headers = OwnedHeaders::new();
+                for (key, value) in inp_headers {
+                    headers = headers.insert(Header {
+                        key: &key,
+                        value: Some(&value),
+                    });
+                }
+                let mut record: FutureRecord<'_, String, _> = FutureRecord::to(&self.topic)
+                    .headers(headers)
+                    .payload(payload.as_ref());
+
+                // The set_partition_key option comes from the user configuration.
+                // The partition key for a message is constructed by message.keys.join(":").
+                // Messages with same partition key will always go to the same partition on Kafka.
+                // If the partition key is not provided, the message will be sent to a random partition.
+                if self.set_partition_key {
+                    if let Some(ref partition_key) = partition_key {
+                        record = record.key(partition_key);
+                    }
+                }
+                match self.producer.send(record, Duration::from_secs(1)).await {
+                    Ok(_) => KafkaSinkResponse { id, status: Ok(()) },
+                    Err(e) => {
+                        tracing::error!(?e, "Sending payload to Kafka topic");
+                        KafkaSinkResponse {
+                            id,
+                            status: Err(crate::Error::Kafka(format!(
+                                "Sending payload to kafka: {e:?}"
+                            ))),
+                        }
+                    }
+                }
+            };
+            send_futures.push(fut);
+        }
+        let mut results = Vec::with_capacity(message_count);
+        while let Some(status) = send_futures.next().await {
+            results.push(status);
+        }
+        Ok(results)
+    }
+}
+
+/// Expose methods so that numaflow-core crate doesn't have to depend on rdkafka.
+#[cfg(feature = "kafka-tests-utils")]
+pub mod test_utils {
+    use rdkafka::ClientConfig;
+    use rdkafka::admin::{AdminClient, AdminOptions, NewTopic, TopicReplication};
+    use rdkafka::consumer::{Consumer, StreamConsumer};
+    use rdkafka::message::Headers;
+    use rdkafka::message::Message;
+    use rdkafka::producer::FutureProducer;
+    use std::collections::HashMap;
+    use std::time::Duration;
+    use tokio::time::timeout;
+    use uuid::Uuid;
+
+    pub async fn setup_test_topic() -> (FutureProducer, String) {
+        let producer: FutureProducer = ClientConfig::new()
+            .set("bootstrap.servers", "localhost:9092")
+            .create()
+            .expect("Failed to create producer");
+
+        let topic_name = format!(
+            "kafka_sink_test_topic_{}",
+            Uuid::new_v4().to_string().replace("-", "")
+        );
+
+        // Create topic if it doesn't exist
+        let admin_client = ClientConfig::new()
+            .set("bootstrap.servers", "localhost:9092")
+            .create::<AdminClient<_>>()
+            .expect("Failed to create admin client");
+
+        let topic_config = NewTopic::new(topic_name.as_str(), 1, TopicReplication::Fixed(1));
+        let _ = admin_client
+            .create_topics(&[topic_config], &AdminOptions::new())
+            .await
+            .expect("Failed to create topic");
+
+        (producer, topic_name)
+    }
+
+    /// The message received from Kafka.
+    pub struct TestKafkaMessage {
+        pub headers: HashMap<String, String>,
+        pub payload: Vec<u8>,
+    }
+
+    pub async fn consume_messages_from_topic(topic: &str, count: usize) -> Vec<TestKafkaMessage> {
+        let consumer: StreamConsumer = ClientConfig::new()
+            .set("bootstrap.servers", "localhost:9092")
+            .set("group.id", format!("test-consumer-{}", Uuid::new_v4()))
+            .set("auto.offset.reset", "earliest")
+            .create()
+            .expect("Failed to create consumer");
+        consumer.subscribe(&[topic]).expect("Failed to subscribe");
+        let mut messages = Vec::with_capacity(count);
+        let mut received = 0;
+        while received < count {
+            let result = timeout(Duration::from_secs(10), consumer.recv()).await;
+            assert!(result.is_ok(), "Did not receive message from Kafka");
+            let msg = result.unwrap().expect("Kafka error");
+            let payload = msg.payload().unwrap_or(&[]).to_vec();
+            let mut headers = HashMap::new();
+            if let Some(kafka_headers) = msg.headers() {
+                for header in kafka_headers.iter() {
+                    if let (Some(value), key) = (header.value, header.key) {
+                        headers.insert(key.to_string(), String::from_utf8_lossy(value).to_string());
+                    }
+                }
+            }
+            messages.push(TestKafkaMessage { headers, payload });
+            received += 1;
+        }
+        messages
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use bytes::Bytes;
+    use std::collections::HashMap;
+
+    #[cfg(all(feature = "kafka-tests", feature = "kafka-tests-utils"))]
+    #[tokio::test]
+    async fn test_kafka_sink_send_and_consume() {
+        let (_producer, topic_name) = test_utils::setup_test_topic().await;
+        let config = KafkaSinkConfig {
+            brokers: vec!["localhost:9092".to_string()],
+            topic: topic_name.clone(),
+            auth: None,
+            tls: None,
+            set_partition_key: true,
+        };
+        let mut sink = new_sink(config).expect("Failed to create KafkaSink");
+        let mut headers = HashMap::new();
+        headers.insert("header1".to_string(), "value1".to_string());
+        headers.insert("header2".to_string(), "value2".to_string());
+        let msg = KafkaSinkMessage {
+            id: "msg1".to_string(),
+            partition_key: Some("key1".to_string()),
+            headers: headers.clone(),
+            payload: Bytes::from("test-payload"),
+        };
+        let responses = sink
+            .sink_messages(vec![msg])
+            .await
+            .expect("Failed to send messages");
+        assert_eq!(responses.len(), 1);
+        assert!(responses[0].status.is_ok());
+
+        // Now consume the message from Kafka to verify
+        let messages = test_utils::consume_messages_from_topic(&topic_name, 1).await;
+        let msg = &messages[0];
+        assert_eq!(msg.payload, b"test-payload");
+        assert_eq!(msg.headers.get("header1"), Some(&"value1".to_string()));
+        assert_eq!(msg.headers.get("header2"), Some(&"value2".to_string()));
+    }
+
+    #[cfg(feature = "kafka-tests")]
+    #[tokio::test]
+    async fn test_kafka_sink_multiple_messages() {
+        let (_producer, topic_name) = test_utils::setup_test_topic().await;
+        let config = KafkaSinkConfig {
+            brokers: vec!["localhost:9092".to_string()],
+            topic: topic_name.clone(),
+            auth: None,
+            tls: None,
+            set_partition_key: false,
+        };
+        let mut sink = new_sink(config).expect("Failed to create KafkaSink");
+        let mut messages = Vec::new();
+        for i in 0..5 {
+            let mut headers = HashMap::new();
+            headers.insert("header".to_string(), format!("value{}", i));
+            messages.push(KafkaSinkMessage {
+                id: format!("msg{}", i),
+                partition_key: None,
+                headers,
+                payload: Bytes::from(format!("payload-{}", i)),
+            });
+        }
+        let responses = sink
+            .sink_messages(messages)
+            .await
+            .expect("Failed to send messages");
+        assert_eq!(responses.len(), 5);
+        for resp in responses {
+            assert!(resp.status.is_ok());
+        }
+        // Consume all messages
+        let messages = test_utils::consume_messages_from_topic(&topic_name, 5).await;
+        let mut seen_payloads = std::collections::HashSet::new();
+        for msg in messages {
+            seen_payloads.insert(String::from_utf8_lossy(&msg.payload).to_string());
+        }
+        for i in 0..5 {
+            assert!(seen_payloads.contains(&format!("payload-{}", i)));
+        }
+    }
+}
diff --git a/rust/extns/numaflow-kafka/src/source.rs b/rust/extns/numaflow-kafka/src/source.rs
new file mode 100644
index 00000000..bac032e6
--- /dev/null
+++ b/rust/extns/numaflow-kafka/src/source.rs
@@ -0,0 +1,811 @@
+use std::sync::Arc;
+use std::{collections::HashMap, time::Duration};
+
+use bytes::Bytes;
+use rdkafka::Offset;
+use rdkafka::client::ClientContext;
+use rdkafka::config::{ClientConfig, RDKafkaLogLevel};
+use rdkafka::consumer::stream_consumer::StreamConsumer;
+use rdkafka::consumer::{BaseConsumer, CommitMode, Consumer, ConsumerContext, Rebalance};
+use rdkafka::error::KafkaResult;
+use rdkafka::message::{Headers, Message};
+use rdkafka::topic_partition_list::TopicPartitionList;
+use tokio::sync::{mpsc, oneshot};
+use tracing::{error, info, warn};
+
+use crate::{Error, KafkaSaslAuth, Result, TlsConfig};
+
+#[derive(Debug, Clone, PartialEq)]
+pub struct KafkaSourceConfig {
+    /// The list of Kafka brokers to connect to.
+    pub brokers: Vec<String>,
+    /// The Kafka topics to consume messages from.
+    pub topics: Vec<String>,
+    /// The consumer group to use for the Kafka consumer.
+    pub consumer_group: String,
+    /// The authentication mechanism to use for the Kafka consumer.
+    pub auth: Option<KafkaSaslAuth>,
+    /// The TLS configuration for the Kafka consumer.
+    pub tls: Option<TlsConfig>,
+}
+
+/// Message represents a message received from Kafka which can be converted to Numaflow Message.
+#[derive(Debug)]
+pub struct KafkaMessage {
+    /// The topic name.
+    pub topic: String,
+    /// The user payload.
+    pub value: Bytes,
+    /// The partition number.
+    pub partition: i32,
+    /// The offset of the message.
+    pub offset: i64,
+    /// The headers of the message.
+    pub headers: HashMap<String, String>,
+}
+
+// A context can be used to change the behavior of consumers by adding callbacks
+// that will be executed by librdkafka.
+struct KafkaContext;
+
+impl ClientContext for KafkaContext {}
+
+impl ConsumerContext for KafkaContext {
+    fn pre_rebalance(&self, _: &BaseConsumer<Self>, rebalance: &Rebalance<'_>) {
+        info!("Pre rebalance {:?}", rebalance);
+    }
+
+    fn post_rebalance(&self, _: &BaseConsumer<Self>, rebalance: &Rebalance<'_>) {
+        info!("Post rebalance {:?}", rebalance);
+    }
+
+    fn commit_callback(&self, result: KafkaResult<()>, _offsets: &TopicPartitionList) {
+        info!("Committing offsets: {:?}", result);
+    }
+}
+
+/// Represents a Kafka offset for a specific topic.
+#[derive(Debug, Clone, PartialEq, Eq, Hash)]
+pub struct KafkaOffset {
+    /// The topic name
+    pub topic: String,
+    /// The partition id within a topic
+    pub partition: i32,
+    /// The offset of the message within a partition
+    pub offset: i64,
+}
+
+enum KafkaActorMessage {
+    Read {
+        respond_to: oneshot::Sender<Result<Vec<KafkaMessage>>>,
+    },
+    Ack {
+        offsets: Vec<KafkaOffset>,
+        respond_to: oneshot::Sender<Result<()>>,
+    },
+    Pending {
+        respond_to: oneshot::Sender<Result<Option<usize>>>,
+    },
+    PartitionsInfo {
+        respond_to: oneshot::Sender<Result<Vec<i32>>>,
+    },
+}
+
+type NumaflowConsumer = StreamConsumer<KafkaContext>;
+
+struct KafkaActor {
+    consumer: Arc<NumaflowConsumer>,
+    read_timeout: Duration,
+    batch_size: usize,
+    topics: Vec<String>,
+    handler_rx: mpsc::Receiver<KafkaActorMessage>,
+}
+
+impl KafkaActor {
+    async fn start(
+        config: KafkaSourceConfig,
+        batch_size: usize,
+        read_timeout: Duration,
+        handler_rx: mpsc::Receiver<KafkaActorMessage>,
+    ) -> Result<()> {
+        let mut client_config = ClientConfig::new();
+        // https://docs.confluent.io/platform/current/clients/librdkafka/html/md_CONFIGURATION.html
+        client_config
+            .set("group.id", &config.consumer_group)
+            .set("bootstrap.servers", config.brokers.join(","))
+            .set("enable.partition.eof", "false")
+            .set("session.timeout.ms", "6000")
+            .set("enable.auto.commit", "false")
+            .set("auto.offset.reset", "earliest") // TODO: Make this configurable
+            .set_log_level(RDKafkaLogLevel::Warning);
+
+        crate::update_auth_config(&mut client_config, config.tls, config.auth);
+
+        let context = KafkaContext;
+        let consumer: Arc<NumaflowConsumer> =
+            Arc::new(client_config.create_with_context(context).map_err(|err| {
+                Error::Connection {
+                    server: config.brokers.join(","),
+                    error: err.to_string(),
+                }
+            })?);
+
+        // NOTE: Subscribing to a non-existent topic will not return an error
+        // The error happens only when we try to read from the topic
+        // 2025-05-09T02:45:42.239784Z ERROR rdkafka::client: librdkafka: Global error: UnknownTopicOrPartition (Broker: Unknown topic or partition): Subscribed topic not available: test-topic: Broker: Unknown topic or partition
+        // Currently, the pending returns Ok(Some(0)) if the topic is not found. When the topic is created,
+        // the consumer starts to pull messages without the need for a restart.
+        let topics: Vec<&str> = config.topics.iter().map(|s| s.as_str()).collect();
+        consumer
+            .subscribe(&topics)
+            .map_err(|err| Error::Kafka(format!("Failed to subscribe to topic: {}", err)))?;
+
+        // The consumer.subscribe() will not fail even if the credentials are invalid.
+        // To ensure creds/certificates are valid, we make a call to pending_messages() before starting the actor.
+        let mut actor = KafkaActor {
+            consumer,
+            read_timeout,
+            batch_size,
+            topics: config.topics,
+            handler_rx,
+        };
+
+        actor
+            .pending_messages()
+            .await
+            .map_err(|err| Error::Kafka(format!("Failed to get pending messages: {err:?}")))?;
+
+        tokio::spawn(async move {
+            tracing::info!("Starting Kafka consumer...");
+            // This actor terminates when sender end of the handler_rx is closed
+            actor.run().await;
+        });
+
+        Ok(())
+    }
+
+    // run method will only return when the sender end of the handler_rx is closed.
+    async fn run(mut self) {
+        while let Some(msg) = self.handler_rx.recv().await {
+            self.handle_message(msg).await;
+        }
+    }
+
+    async fn handle_message(&mut self, msg: KafkaActorMessage) {
+        match msg {
+            KafkaActorMessage::Read { respond_to } => {
+                let messages = self.read_messages().await;
+                respond_to
+                    .send(messages)
+                    .inspect_err(|e| {
+                        error!(?e, "Failed to send messages from Kafka actor to main task");
+                    })
+                    .expect("Failed to send messages from Kafka actor to main task");
+            }
+            KafkaActorMessage::Ack {
+                offsets,
+                respond_to,
+            } => {
+                let status = self.ack_messages(offsets).await;
+                respond_to
+                    .send(status)
+                    .inspect_err(|e| {
+                        error!(
+                            ?e,
+                            "Failed to send ack messages from Kafka actor to main task"
+                        );
+                    })
+                    .expect("Failed to send ack messages from Kafka actor to main task");
+            }
+            KafkaActorMessage::Pending { respond_to } => {
+                let pending = self.pending_messages().await;
+                respond_to
+                    .send(pending)
+                    .inspect_err(|e| {
+                        error!(
+                            ?e,
+                            "Failed to send pending messages from Kafka actor to main task"
+                        );
+                    })
+                    .expect("Failed to send pending messages from Kafka actor to main task");
+            }
+            KafkaActorMessage::PartitionsInfo { respond_to } => {
+                let partitions = self.partitions_info().await;
+                respond_to
+                    .send(partitions)
+                    .inspect_err(|e| {
+                        error!(
+                            ?e,
+                            "Failed to send partition count from Kafka actor to main task"
+                        );
+                    })
+                    .expect("Failed to send partition count from Kafka actor to main task");
+            }
+        }
+    }
+
+    async fn read_messages(&mut self) -> Result<Vec<KafkaMessage>> {
+        let mut messages: Vec<KafkaMessage> = vec![];
+        let timeout = tokio::time::timeout(self.read_timeout, std::future::pending::<()>());
+        tokio::pin!(timeout);
+
+        // Return error if the number of continuous failures exceeds MAX_FAILURE_COUNT
+        // A successful read will reset the failure count
+        const MAX_FAILURE_COUNT: usize = 10;
+        let mut continuous_failure_count = 0;
+        loop {
+            if messages.len() >= self.batch_size {
+                break;
+            }
+            tokio::select! {
+                biased;
+
+                _ = &mut timeout => {
+                    break;
+                }
+
+                message = self.consumer.recv() => {
+                    let message = match message {
+                        Ok(msg) => {
+                            continuous_failure_count = 0;
+                            msg
+                        }
+                        Err(e) => {
+                            // TODO: Check the error when topic doesn't exist
+                            continuous_failure_count += 1;
+                            if continuous_failure_count > MAX_FAILURE_COUNT {
+                                return Err(Error::Kafka(format!(
+                                    "Failed to read messages after {} retries: {e:?}",
+                                    MAX_FAILURE_COUNT
+                                )));
+                            }
+                            error!(?e, "Failed to read messages, will retry after 100 milliseconds");
+                            tokio::time::sleep(Duration::from_millis(100)).await;
+                            continue;
+                        }
+                    };
+
+                    let mut headers = match message.headers() {
+                        Some(headers) => headers
+                            .iter()
+                            .map(|header| {
+                                (
+                                    header.key.to_string(),
+                                    String::from_utf8_lossy(header.value.unwrap_or_default()).to_string(),
+                                )
+                            })
+                            .collect(),
+                        None => HashMap::new(),
+                    };
+                    headers.insert(crate::KAFKA_TOPIC_HEADER_KEY.to_string(), message.topic().to_string());
+
+                    let value = match message.payload() {
+                        Some(payload) => Bytes::copy_from_slice(payload),
+                        // The rdkafka doc says that the payload can be None if there is no payload.
+                        None => Bytes::new(),
+                    };
+
+                    let message = KafkaMessage {
+                        topic: message.topic().to_string(),
+                        value,
+                        partition: message.partition(),
+                        offset: message.offset(),
+                        headers,
+                    };
+
+                    messages.push(message);
+                }
+            }
+        }
+        tracing::debug!(msg_count = messages.len(), "Read messages from Kafka");
+        Ok(messages)
+    }
+
+    async fn ack_messages(&mut self, offsets: Vec<KafkaOffset>) -> Result<()> {
+        use std::collections::HashMap;
+        // topic -> partition -> offset
+        let mut topic_partition_offsets: HashMap<String, HashMap<i32, i64>> = HashMap::new();
+
+        // For each topic and partition, keep only the highest offset
+        for kafka_offset in offsets {
+            topic_partition_offsets
+                .entry(kafka_offset.topic.clone())
+                .or_default()
+                .entry(kafka_offset.partition)
+                .and_modify(|current| {
+                    if kafka_offset.offset > *current {
+                        *current = kafka_offset.offset;
+                    }
+                })
+                .or_insert(kafka_offset.offset);
+        }
+
+        let mut ack_tasks = vec![];
+        for (topic, partition_offsets) in topic_partition_offsets {
+            let mut tpl = TopicPartitionList::new();
+            for (partition, offset) in partition_offsets {
+                // Commit offset+1 (as per Kafka semantics)
+                // In Kafka, the offset represents the position of the next message to be read.
+                // When we commit offset N, it means the next message to be read will be at offset N.
+                // Since we've already processed the message at the current offset, we need to commit
+                // offset+1 to indicate we want to read the next message in the partition.
+                tpl.add_partition_offset(&topic, partition, Offset::Offset(offset + 1))
+                    .map_err(|e| {
+                        Error::Kafka(format!(
+                            "Failed to add partition offset for acknowledging messages: {}",
+                            e
+                        ))
+                    })?;
+            }
+            // commit internally calls [rd_kafka_offset_store](https://docs.confluent.io/platform/current/clients/librdkafka/html/rdkafka_8h.html#ab96539928328f14c3c9177ea0c896c87)
+            // This may be a blocking call, so we spawn a new task to run it.
+            let consumer = Arc::clone(&self.consumer);
+            let task = tokio::task::spawn_blocking(move || {
+                consumer
+                    .commit(&tpl, CommitMode::Sync)
+                    .map_err(|e| Error::Kafka(format!("Failed to commit offsets: {}", e)))
+            });
+            ack_tasks.push(task);
+        }
+        for task in ack_tasks {
+            task.await.map_err(|e| {
+                Error::Kafka(format!("Waiting for spawned ack tasks to complete: {e:?}"))
+            })??;
+        }
+        Ok(())
+    }
+
+    /// Returns the total number of pending messages across all topics and partitions.
+    /// The total pending is calculated as the sum of (high watermark - committed offset)
+    /// for each partition across all topics.
+    async fn pending_messages(&mut self) -> Result<Option<usize>> {
+        let timeout = Duration::from_secs(5);
+        let mut handles = Vec::new();
+        for topic in &self.topics {
+            let consumer = Arc::clone(&self.consumer);
+            let topic = topic.clone();
+
+            // fetch_metadata internally calls [rd_kafka_metadata](https://docs.confluent.io/platform/current/clients/librdkafka/html/rdkafka_8h.html#a84bba4a4b13fdb515f1a22d6fd4f7344)
+            // This may be a blocking call, so we spawn a new task to run it.
+            handles.push(tokio::task::spawn_blocking(move || {
+                let metadata = consumer
+                    .fetch_metadata(Some(&topic), timeout)
+                    .map_err(|e| Error::Kafka(format!("Failed to fetch metadata: {}", e)))?;
+                let Some(topic_metadata) = metadata.topics().first() else {
+                    warn!(topic = topic, "No topic metadata found");
+                    return Ok(0);
+                };
+                let mut topic_pending = 0;
+                for partition in 0..topic_metadata.partitions().len() {
+                    let mut tpl = TopicPartitionList::new();
+                    tpl.add_partition(&topic, partition as i32);
+                    let committed = consumer.committed_offsets(tpl, timeout).map_err(|e| {
+                        Error::Kafka(format!("Failed to get committed offsets: {}", e))
+                    })?;
+                    let (low, high) = consumer
+                        .fetch_watermarks(&topic, partition as i32, timeout)
+                        .map_err(|e| Error::Kafka(format!("Failed to fetch watermarks: {}", e)))?;
+                    let committed_offset = match committed.elements_for_topic(&topic).first() {
+                        Some(element) => match element.offset() {
+                            Offset::Offset(offset) => offset,
+                            _ => low,
+                        },
+                        None => low,
+                    };
+                    topic_pending += (high - committed_offset) as usize;
+                }
+                Ok(topic_pending)
+            }));
+        }
+        let mut total_pending = 0;
+        for handle in handles {
+            match handle.await {
+                Ok(Ok(count)) => total_pending += count,
+                Ok(Err(e)) => {
+                    error!(?e, "Error fetching pending messages");
+                    return Err(e);
+                }
+                Err(e) => {
+                    error!(?e, "Tokio task join error fetching pending messages");
+                    return Err(Error::Other(format!("Tokio task join error: {e}")));
+                }
+            }
+        }
+        Ok(Some(total_pending))
+    }
+
+    /// Returns the partition IDs of the topic with the maximum number of partitions.
+    /// Since we support specifying multiple topics, this method returns the Vec of partition IDs
+    /// from the topic that has the most partitions among all configured topics.
+    async fn partitions_info(&mut self) -> Result<Vec<i32>> {
+        let timeout = Duration::from_secs(5);
+        let mut handles = Vec::new();
+        for topic in &self.topics {
+            let consumer = Arc::clone(&self.consumer);
+            let topic = topic.clone();
+
+            // fetch_metadata internally calls [rd_kafka_metadata](https://docs.confluent.io/platform/current/clients/librdkafka/html/rdkafka_8h.html#a84bba4a4b13fdb515f1a22d6fd4f7344)
+            // This may be a blocking call, so we spawn a new task to run it.
+            handles.push(tokio::task::spawn_blocking(move || {
+                let metadata = consumer
+                    .fetch_metadata(Some(&topic), timeout)
+                    .map_err(|e| Error::Kafka(format!("Failed to fetch metadata: {}", e)))?;
+                let Some(topic_metadata) = metadata.topics().first() else {
+                    warn!(topic = topic, "No topic metadata found");
+                    return Ok(Vec::new());
+                };
+                let partitions: Vec<i32> =
+                    topic_metadata.partitions().iter().map(|p| p.id()).collect();
+                Ok(partitions)
+            }));
+        }
+        let mut max_partitions = 0;
+        let mut result: Vec<i32> = Vec::new();
+        for handle in handles {
+            match handle.await {
+                Ok(Ok(partitions)) => {
+                    if partitions.len() > max_partitions {
+                        max_partitions = partitions.len();
+                        result = partitions;
+                    }
+                }
+                Ok(Err(e)) => {
+                    error!(?e, "Error fetching partitions info");
+                    return Err(e);
+                }
+                Err(e) => {
+                    error!(?e, "Tokio task join error fetching partitions info");
+                    return Err(Error::Other(format!("Tokio task join error: {e}")));
+                }
+            }
+        }
+        Ok(result)
+    }
+}
+
+#[derive(Clone)]
+pub struct KafkaSource {
+    actor_tx: mpsc::Sender<KafkaActorMessage>,
+}
+
+impl KafkaSource {
+    pub async fn connect(
+        config: KafkaSourceConfig,
+        batch_size: usize,
+        read_timeout: Duration,
+    ) -> Result<Self> {
+        let (tx, rx) = mpsc::channel(10);
+        KafkaActor::start(config, batch_size, read_timeout, rx).await?;
+        Ok(Self { actor_tx: tx })
+    }
+
+    pub async fn read_messages(&self) -> Result<Vec<KafkaMessage>> {
+        let (tx, rx) = oneshot::channel();
+        let msg = KafkaActorMessage::Read { respond_to: tx };
+        let _ = self.actor_tx.send(msg).await;
+        rx.await
+            .map_err(|_| Error::Other("Actor task terminated".into()))?
+    }
+
+    pub async fn ack_messages(&self, offsets: Vec<KafkaOffset>) -> Result<()> {
+        let (tx, rx) = oneshot::channel();
+        let msg = KafkaActorMessage::Ack {
+            offsets,
+            respond_to: tx,
+        };
+        let _ = self.actor_tx.send(msg).await;
+        rx.await
+            .map_err(|_| Error::Other("Actor task terminated".into()))?
+    }
+
+    pub async fn pending_messages(&self) -> Result<Option<usize>> {
+        let (tx, rx) = oneshot::channel();
+        let msg = KafkaActorMessage::Pending { respond_to: tx };
+        let _ = self.actor_tx.send(msg).await;
+        rx.await
+            .map_err(|_| Error::Other("Actor task terminated".into()))?
+    }
+
+    pub async fn partitions_info(&self) -> Result<Vec<i32>> {
+        let (tx, rx) = oneshot::channel();
+        let msg = KafkaActorMessage::PartitionsInfo { respond_to: tx };
+        let _ = self.actor_tx.send(msg).await;
+        rx.await
+            .map_err(|_| Error::Other("Actor task terminated".into()))?
+    }
+}
+
+/// Expose methods so that numaflow-core crate doesn't have to depend on rdkafka.
+#[cfg(feature = "kafka-tests-utils")]
+pub mod test_utils {
+    use super::*;
+    use rdkafka::producer::{FutureProducer, FutureRecord};
+    use std::time::Duration;
+
+    pub async fn setup_test_topic() -> (FutureProducer, String) {
+        let producer: FutureProducer = ClientConfig::new()
+            .set("bootstrap.servers", "localhost:9092")
+            .create()
+            .expect("Failed to create producer");
+
+        let topic_name = format!(
+            "kafka_source_test_topic_{}",
+            uuid::Uuid::new_v4().to_string().replace("-", "")
+        );
+
+        // Create topic if it doesn't exist
+        let admin_client = ClientConfig::new()
+            .set("bootstrap.servers", "localhost:9092")
+            .create::<rdkafka::admin::AdminClient<_>>()
+            .expect("Failed to create admin client");
+
+        let topic_config = rdkafka::admin::NewTopic::new(
+            topic_name.as_str(),
+            1,
+            rdkafka::admin::TopicReplication::Fixed(1),
+        );
+        let _ = admin_client
+            .create_topics(&[topic_config], &rdkafka::admin::AdminOptions::new())
+            .await
+            .expect("Failed to create topic");
+
+        (producer, topic_name)
+    }
+
+    pub async fn produce_test_messages(producer: &FutureProducer, topic: &str, count: usize) {
+        for i in 0..count {
+            let payload = format!("message {}", i);
+            let key = format!("key {}", i);
+            let record = FutureRecord::to(topic).payload(&payload).key(&key);
+            producer
+                .send(record, Duration::from_secs(5))
+                .await
+                .expect("Failed to send message");
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use rdkafka::message::{Header, OwnedHeaders};
+    use rdkafka::producer::FutureRecord;
+    use tokio::time::Instant;
+
+    #[cfg(all(feature = "kafka-tests", feature = "kafka-tests-utils"))]
+    #[tokio::test]
+    async fn test_kafka_source() {
+        let (producer, topic_name) = test_utils::setup_test_topic().await;
+
+        // Produce 100 messages
+        test_utils::produce_test_messages(&producer, &topic_name, 100).await;
+
+        let config = KafkaSourceConfig {
+            brokers: vec!["localhost:9092".to_string()],
+            topics: vec![topic_name.clone()],
+            consumer_group: "test_consumer_group".to_string(),
+            auth: None,
+            tls: None,
+        };
+
+        let read_timeout = Duration::from_secs(5);
+        let source = KafkaSource::connect(config, 30, read_timeout)
+            .await
+            .expect("Failed to connect to Kafka");
+
+        let pending = source
+            .pending_messages()
+            .await
+            .expect("Failed to get pending messages");
+        assert_eq!(
+            pending,
+            Some(100),
+            "Pending messages should include unacknowledged messages"
+        );
+
+        // Read messages
+        let messages = source
+            .read_messages()
+            .await
+            .expect("Failed to read messages");
+        assert_eq!(messages.len(), 30);
+        let pending = source
+            .pending_messages()
+            .await
+            .expect("Failed to get pending messages");
+        assert_eq!(
+            pending,
+            Some(100),
+            "Pending messages should include unacknowledged messages"
+        );
+
+        // Ack messages
+        let offsets: Vec<KafkaOffset> = messages
+            .iter()
+            .map(|msg| KafkaOffset {
+                topic: topic_name.clone(),
+                partition: msg.partition,
+                offset: msg.offset,
+            })
+            .collect();
+        source
+            .ack_messages(offsets)
+            .await
+            .expect("Failed to ack messages");
+
+        tokio::time::sleep(Duration::from_millis(5)).await;
+        let pending = source
+            .pending_messages()
+            .await
+            .expect("Failed to get pending messages");
+        assert_eq!(
+            pending,
+            Some(70),
+            "Pending messages should be 70 after acking 30 messages"
+        );
+
+        // Read remaining messages
+        let messages = source
+            .read_messages()
+            .await
+            .expect("Failed to read messages");
+        assert_eq!(messages.len(), 30);
+
+        // Ack remaining messages
+        let offsets: Vec<KafkaOffset> = messages
+            .iter()
+            .map(|msg| KafkaOffset {
+                topic: topic_name.clone(),
+                partition: msg.partition,
+                offset: msg.offset,
+            })
+            .collect();
+        source
+            .ack_messages(offsets)
+            .await
+            .expect("Failed to ack messages");
+
+        // Check pending messages
+        tokio::time::sleep(Duration::from_millis(5)).await;
+        let pending = source
+            .pending_messages()
+            .await
+            .expect("Failed to get pending messages");
+        assert_eq!(
+            pending,
+            Some(40),
+            "Pending messages should be 40 after acking another 30 messages"
+        );
+
+        // Read remaining messages
+        let messages = source
+            .read_messages()
+            .await
+            .expect("Failed to read messages");
+        assert_eq!(messages.len(), 30);
+
+        // Ack remaining messages
+        let offsets: Vec<KafkaOffset> = messages
+            .iter()
+            .map(|msg| KafkaOffset {
+                topic: topic_name.clone(),
+                partition: msg.partition,
+                offset: msg.offset,
+            })
+            .collect();
+        source
+            .ack_messages(offsets)
+            .await
+            .expect("Failed to ack messages");
+
+        // Check pending messages
+        tokio::time::sleep(Duration::from_millis(5)).await;
+        let pending = source
+            .pending_messages()
+            .await
+            .expect("Failed to get pending messages");
+        assert_eq!(
+            pending,
+            Some(10),
+            "Pending messages should be 10 after acking another 30 messages"
+        );
+
+        // Read remaining messages
+        let messages = source
+            .read_messages()
+            .await
+            .expect("Failed to read messages");
+        assert_eq!(messages.len(), 10);
+
+        // Ack remaining messages
+        let offsets: Vec<KafkaOffset> = messages
+            .iter()
+            .map(|msg| KafkaOffset {
+                topic: topic_name.clone(),
+                partition: msg.partition,
+                offset: msg.offset,
+            })
+            .collect();
+        source
+            .ack_messages(offsets)
+            .await
+            .expect("Failed to ack messages");
+
+        // Check pending messages
+        tokio::time::sleep(Duration::from_millis(5)).await;
+        let pending = source
+            .pending_messages()
+            .await
+            .expect("Failed to get pending messages");
+        assert_eq!(
+            pending,
+            Some(0),
+            "Pending messages should be 0 after acking all messages"
+        );
+
+        // Ensure read operation returns after the read timeout
+        let start = Instant::now();
+        let messages = source
+            .read_messages()
+            .await
+            .expect("Failed to read messages");
+        let elapsed = start.elapsed();
+        assert!(
+            elapsed < read_timeout + Duration::from_millis(100),
+            "Read operation should return in 1 second"
+        );
+        assert!(
+            messages.is_empty(),
+            "No messages should be returned after all messages are acked"
+        );
+    }
+
+    #[cfg(all(feature = "kafka-tests", feature = "kafka-tests-utils"))]
+    #[tokio::test]
+    async fn test_kafka_source_with_headers() {
+        let (producer, topic_name) = test_utils::setup_test_topic().await;
+
+        // Produce a message with headers
+        let headers = OwnedHeaders::new()
+            .insert(Header {
+                key: "header1",
+                value: Some("value1"),
+            })
+            .insert(Header {
+                key: "header2",
+                value: Some("value2"),
+            });
+
+        let record = FutureRecord::to(&topic_name)
+            .payload("test message")
+            .key("test key")
+            .headers(headers);
+
+        producer
+            .send(record, Duration::from_secs(5))
+            .await
+            .expect("Failed to send message");
+
+        let config = KafkaSourceConfig {
+            brokers: vec!["localhost:9092".to_string()],
+            topics: vec![topic_name.clone()],
+            consumer_group: "test_consumer_group_headers".to_string(),
+            auth: None,
+            tls: None,
+        };
+
+        let source = KafkaSource::connect(config, 1, Duration::from_secs(5))
+            .await
+            .expect("Failed to connect to Kafka");
+
+        let messages = source
+            .read_messages()
+            .await
+            .expect("Failed to read messages");
+        assert_eq!(messages.len(), 1);
+
+        let message = &messages[0];
+        assert_eq!(message.headers.get("header1"), Some(&"value1".to_string()));
+        assert_eq!(message.headers.get("header2"), Some(&"value2".to_string()));
+    }
+}
diff --git a/rust/extns/numaflow-sqs/src/lib.rs b/rust/extns/numaflow-sqs/src/lib.rs
index 4b0ce526..721d15b3 100644
--- a/rust/extns/numaflow-sqs/src/lib.rs
+++ b/rust/extns/numaflow-sqs/src/lib.rs
@@ -5,9 +5,21 @@
 //! - Error propagation and handling for AWS SDK errors
 //! - Actor-based concurrency model for thread safety
 //! - Clean abstraction of SQS operations
+use aws_config::meta::region::RegionProviderChain;
+use aws_config::{BehaviorVersion, Region};
+use aws_sdk_sqs::Client;
 use tokio::sync::oneshot;
 pub mod source;
 
+pub mod sink;
+
+/// Configuration for SQS client creation
+#[derive(Debug)]
+pub enum SqsConfig {
+    Source(source::SqsSourceConfig),
+    Sink(sink::SqsSinkConfig),
+}
+
 /// Custom error types for the SQS client library.
 ///
 /// Design goals:
@@ -29,7 +41,16 @@ pub enum Error {
     Other(String),
 }
 
-pub type Result<T> = core::result::Result<T, Error>;
+#[derive(thiserror::Error, Debug)]
+pub enum SqsSourceError {
+    #[error("SQS Source Error: {0}")]
+    Error(#[from] Error),
+}
+#[derive(thiserror::Error, Debug)]
+pub enum SqsSinkError {
+    #[error("SQS Sink Error: {0}")]
+    Error(#[from] Error),
+}
 
 impl From<String> for Error {
     fn from(value: String) -> Self {
@@ -37,6 +58,53 @@ impl From<String> for Error {
     }
 }
 
+impl From<SqsSourceError> for Error {
+    fn from(value: SqsSourceError) -> Self {
+        match value {
+            SqsSourceError::Error(err) => err,
+        }
+    }
+}
+
+impl From<SqsSinkError> for Error {
+    fn from(value: SqsSinkError) -> Self {
+        match value {
+            SqsSinkError::Error(err) => err,
+        }
+    }
+}
+
+/// Creates and configures an SQS client based on the provided configuration.
+pub async fn create_sqs_client(config: SqsConfig) -> Result<Client, Error> {
+    // Get the region and validate the configuration based on variant
+    let region = match &config {
+        SqsConfig::Source(cfg) => cfg.region,
+        SqsConfig::Sink(cfg) => cfg.region,
+    };
+
+    let region_provider = RegionProviderChain::first_try(Region::new(region))
+        .or_default_provider()
+        .or_else(Region::new("us-west-2")); // Default region if none provided
+
+    // recommended to pin behavior version
+    // https://docs.aws.amazon.com/sdk-for-rust/latest/dg/behavior-versions.html
+    let mut config_builder =
+        aws_config::defaults(BehaviorVersion::v2025_01_17()).region(region_provider);
+
+    // Apply endpoint URL if configured for source
+    if let SqsConfig::Source(cfg) = &config {
+        if let Some(endpoint_url) = &cfg.endpoint_url {
+            config_builder = config_builder.endpoint_url(endpoint_url);
+        }
+    }
+
+    // Load the shared config
+    let shared_config = config_builder.load().await;
+
+    // Create and return the client
+    Ok(Client::new(&shared_config))
+}
+
 #[cfg(test)]
 mod tests {
     use aws_config::BehaviorVersion;
diff --git a/rust/extns/numaflow-sqs/src/sink.rs b/rust/extns/numaflow-sqs/src/sink.rs
new file mode 100644
index 00000000..70612598
--- /dev/null
+++ b/rust/extns/numaflow-sqs/src/sink.rs
@@ -0,0 +1,492 @@
+/// Module for handling AWS SQS sink operations, allowing messages to be sent to SQS queues.
+///
+/// This module provides functionality to configure and use AWS SQS as a sink for messaging.
+use aws_sdk_sqs::Client;
+use aws_sdk_sqs::types::SendMessageBatchRequestEntry;
+use bytes::Bytes;
+
+use crate::{Error, SqsConfig, SqsSinkError};
+
+pub const SQS_DEFAULT_REGION: &str = "us-west-2";
+
+pub type Result<T> = std::result::Result<T, SqsSinkError>;
+
+/// Configuration for the AWS SQS sink.
+#[derive(Clone, Debug, PartialEq)]
+pub struct SqsSinkConfig {
+    /// AWS region where the SQS queue is located
+    pub region: &'static str,
+    /// Name of the SQS queue
+    pub queue_name: &'static str,
+    /// AWS account ID of the queue owner
+    pub queue_owner_aws_account_id: &'static str,
+}
+
+/// Message to be sent to SQS.
+pub struct SqsSinkMessage {
+    /// Unique identifier for the message
+    pub id: String,
+    /// Message body content as bytes
+    pub message_body: Bytes,
+}
+
+/// Main SQS sink client that handles sending messages to SQS.
+#[derive(Clone)]
+pub struct SqsSink {
+    client: Client,
+    queue_url: &'static str,
+}
+
+/// Builder for creating and configuring an SQS sink.
+#[derive(Clone)]
+pub struct SqsSinkBuilder {
+    config: SqsSinkConfig,
+    client: Option<Client>,
+}
+/// Response from sending a message to SQS.
+#[derive(Debug)]
+pub struct SqsSinkResponse {
+    /// ID of the message that was sent
+    pub id: String,
+    /// Status of the send operation
+    pub status: Result<()>,
+    /// Error code if any
+    pub code: Option<String>,
+    /// Indicates if the error was caused by the sender
+    pub sender_fault: Option<bool>,
+}
+
+impl Default for SqsSinkBuilder {
+    fn default() -> Self {
+        Self::new(SqsSinkConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "",
+            queue_owner_aws_account_id: "",
+        })
+    }
+}
+
+impl SqsSinkBuilder {
+    pub fn new(config: SqsSinkConfig) -> Self {
+        Self {
+            config,
+            client: None,
+        }
+    }
+
+    pub fn config(mut self, config: SqsSinkConfig) -> Self {
+        self.config = config;
+        self
+    }
+
+    pub fn client(mut self, client: Client) -> Self {
+        self.client = Some(client);
+        self
+    }
+
+    pub async fn build(self) -> Result<SqsSink> {
+        let sqs_client = match self.client {
+            Some(client) => client,
+            None => crate::create_sqs_client(SqsConfig::Sink(self.config.clone())).await?,
+        };
+
+        let queue_name = self.config.queue_name;
+        let queue_owner_aws_account_id = self.config.queue_owner_aws_account_id;
+
+        let get_queue_url_output = sqs_client
+            .clone()
+            .get_queue_url()
+            .queue_name(queue_name)
+            .queue_owner_aws_account_id(queue_owner_aws_account_id)
+            .send()
+            .await
+            .map_err(|err| Error::Sqs(err.into()))?;
+
+        let queue_url = get_queue_url_output
+            .queue_url
+            .ok_or_else(|| Error::Other("Queue URL not found".to_string()))?;
+
+        tracing::info!(queue_url = queue_url.clone(), "Queue URL found");
+
+        Ok(SqsSink {
+            client: sqs_client.clone(),
+            queue_url: Box::leak(queue_url.clone().to_string().into_boxed_str()),
+        })
+    }
+}
+
+impl SqsSink {
+    /// Sends a batch of messages to the SQS queue.
+    ///
+    /// Returns responses for each message, including success or failure status.
+    pub async fn sink_messages(
+        &self,
+        messages: Vec<SqsSinkMessage>,
+    ) -> Result<Vec<SqsSinkResponse>> {
+        let mut entries = Vec::with_capacity(messages.len());
+        let mut sink_message_responses = Vec::with_capacity(messages.len());
+        for message in messages {
+            let entry = SendMessageBatchRequestEntry::builder()
+                .id(message.id)
+                .message_body(String::from_utf8_lossy(&message.message_body).to_string())
+                .build();
+
+            match entry {
+                Ok(entry) => {
+                    tracing::debug!("SQS message entry created: {:?}", entry);
+                    entries.push(entry);
+                }
+                Err(err) => {
+                    tracing::error!("Failed to create SQS message entry: {:?}", err);
+                    return Err(SqsSinkError::from(Error::Sqs(err.into())));
+                }
+            }
+        }
+
+        let send_message_batch_output = self
+            .client
+            .send_message_batch()
+            .queue_url(self.queue_url)
+            .set_entries(Some(entries))
+            .send()
+            .await;
+
+        match send_message_batch_output {
+            Ok(output) => {
+                for succeeded in output.successful {
+                    let id = succeeded.id;
+                    let status = Ok(());
+                    sink_message_responses.push(SqsSinkResponse {
+                        id,
+                        status,
+                        code: None,
+                        sender_fault: None,
+                    });
+                }
+
+                for failed in output.failed {
+                    let id = failed.id;
+                    let status = Err(SqsSinkError::from(Error::Other(
+                        failed.message.unwrap_or_default().to_string(),
+                    )));
+                    sink_message_responses.push(SqsSinkResponse {
+                        id,
+                        status,
+                        code: Some(failed.code),
+                        sender_fault: Some(failed.sender_fault),
+                    });
+                }
+
+                Ok(sink_message_responses)
+            }
+            Err(err) => {
+                tracing::error!("Failed to send messages: {:?}", err);
+                Err(SqsSinkError::from(Error::Sqs(err.into())))
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use aws_config::BehaviorVersion;
+    use aws_sdk_sqs::types::BatchResultErrorEntry;
+    use aws_sdk_sqs::{Client, Config};
+    use aws_smithy_mocks_experimental::{MockResponseInterceptor, Rule, RuleMode, mock};
+    use aws_smithy_types::error::ErrorMetadata;
+    use bytes::Bytes;
+    use test_log::test;
+
+    use crate::sink::{SqsSinkBuilder, SqsSinkConfig, SqsSinkMessage};
+    use crate::source::SQS_DEFAULT_REGION;
+    use crate::{Error, SqsConfig, SqsSinkError};
+
+    #[test(tokio::test)]
+    async fn test_client_creation_with_defaults() {
+        let config = SqsSinkConfig {
+            region: "us-west-2",
+            queue_name: "test-queue",
+            queue_owner_aws_account_id: "123456789012",
+        };
+
+        let result = crate::create_sqs_client(SqsConfig::Sink(config.clone())).await;
+        assert!(result.is_ok());
+    }
+
+    #[test(tokio::test)]
+    async fn test_sqs_sink_builder() {
+        // test default
+        let builder = SqsSinkBuilder::default();
+        assert_eq!(builder.config.region, SQS_DEFAULT_REGION);
+        assert_eq!(builder.config.queue_name, "");
+
+        let queue_url_output = get_queue_url_output();
+
+        let sqs_operation_mocks = MockResponseInterceptor::new()
+            .rule_mode(RuleMode::MatchAny)
+            .with_rule(&queue_url_output);
+
+        let sqs_mock_client =
+            Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
+
+        let config = SqsSinkConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
+        };
+
+        let sink = SqsSinkBuilder::new(config.clone())
+            .client(sqs_mock_client)
+            .build()
+            .await;
+        assert!(sink.is_ok());
+
+        let sink = sink.unwrap();
+        assert_eq!(
+            sink.queue_url,
+            "https://sqs.us-west-2.amazonaws.com/926113353675/test-q/"
+        );
+    }
+
+    #[test(tokio::test)]
+    async fn test_sqs_sink_send_messages() {
+        let queue_url_output = get_queue_url_output();
+        let send_message_output = get_send_message_output();
+
+        let sqs_operation_mocks = MockResponseInterceptor::new()
+            .rule_mode(RuleMode::MatchAny)
+            .with_rule(&queue_url_output)
+            .with_rule(&send_message_output);
+
+        let sqs_mock_client =
+            Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
+
+        let config = SqsSinkConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
+        };
+
+        let sink = SqsSinkBuilder::new(config.clone())
+            .client(sqs_mock_client)
+            .build()
+            .await;
+        assert!(sink.is_ok());
+
+        let sink = sink.unwrap();
+        let messages = vec![SqsSinkMessage {
+            id: "1".to_string(),
+            message_body: Bytes::from("test message"),
+        }];
+
+        let result = sink.sink_messages(messages).await;
+        assert!(result.is_ok());
+
+        let responses = result.unwrap();
+        assert_eq!(responses.len(), 1);
+        assert_eq!(responses[0].id, "1");
+        assert!(responses[0].status.is_ok());
+        assert_eq!(responses[0].code, None);
+        assert_eq!(responses[0].sender_fault, None);
+    }
+
+    #[test(tokio::test)]
+    async fn test_sqs_sink_send_messages_with_failed() {
+        let queue_url_output = get_queue_url_output();
+        let send_message_output = get_send_message_output_with_failed();
+
+        let sqs_operation_mocks = MockResponseInterceptor::new()
+            .rule_mode(RuleMode::MatchAny)
+            .with_rule(&queue_url_output)
+            .with_rule(&send_message_output);
+
+        let sqs_mock_client =
+            Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
+
+        let config = SqsSinkConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
+        };
+
+        let sink = SqsSinkBuilder::new(config.clone())
+            .client(sqs_mock_client)
+            .build()
+            .await;
+        assert!(sink.is_ok());
+
+        let sink = sink.unwrap();
+        let messages = vec![SqsSinkMessage {
+            id: "1".to_string(),
+            message_body: Bytes::from("test message"),
+        }];
+
+        let result = sink.sink_messages(messages).await;
+        assert!(result.is_ok());
+
+        let responses = result.unwrap();
+        assert_eq!(responses.len(), 2);
+        assert_eq!(responses[0].id, "1");
+        assert!(responses[0].status.is_ok());
+        assert_eq!(responses[0].code, None);
+        assert_eq!(responses[0].sender_fault, None);
+        assert_eq!(responses[1].id, "2");
+        assert!(responses[1].status.is_err());
+        assert_eq!(responses[1].code, Some("InvalidParameterValue".to_string()));
+        assert_eq!(responses[1].sender_fault, Some(true));
+    }
+    #[test(tokio::test)]
+    async fn test_sqs_sink_send_messages_all_fail() {
+        let queue_url_output = get_queue_url_output();
+        let send_message_output = get_send_message_output_all_fail();
+
+        let sqs_operation_mocks = MockResponseInterceptor::new()
+            .rule_mode(RuleMode::MatchAny)
+            .with_rule(&queue_url_output)
+            .with_rule(&send_message_output);
+
+        let sqs_mock_client =
+            Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
+
+        let config = SqsSinkConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
+        };
+
+        let sink = SqsSinkBuilder::new(config.clone())
+            .client(sqs_mock_client)
+            .build()
+            .await;
+        assert!(sink.is_ok());
+
+        let sink = sink.unwrap();
+        let messages = vec![SqsSinkMessage {
+            id: "1".to_string(),
+            message_body: Bytes::from("test message"),
+        }];
+
+        let result = sink.sink_messages(messages).await;
+        assert!(result.is_err());
+
+        let error = result.unwrap_err();
+        assert_eq!(
+            error.to_string(),
+            "SQS Sink Error: Failed with SQS error - unhandled error (InvalidParameterValue)"
+        );
+        assert!(matches!(error, SqsSinkError::Error(Error::Sqs(_))));
+    }
+
+    fn get_queue_url_output() -> Rule {
+        let queue_url_output = mock!(aws_sdk_sqs::Client::get_queue_url)
+            .match_requests(|inp| inp.queue_name().unwrap() == "test-q")
+            .then_output(|| {
+                aws_sdk_sqs::operation::get_queue_url::GetQueueUrlOutput::builder()
+                    .queue_url("https://sqs.us-west-2.amazonaws.com/926113353675/test-q/")
+                    .build()
+            });
+        queue_url_output
+    }
+
+    fn get_send_message_output() -> Rule {
+        let successful = aws_sdk_sqs::types::SendMessageBatchResultEntry::builder()
+            .id("1")
+            .message_id("msg-id-1")
+            .md5_of_message_body("f11a425906289abf8cce1733622834c8")
+            .build()
+            .unwrap();
+
+        let send_message_output = mock!(aws_sdk_sqs::Client::send_message_batch)
+            .match_requests(|inp| {
+                inp.queue_url().unwrap()
+                    == "https://sqs.us-west-2.amazonaws.com/926113353675/test-q/"
+            })
+            .then_output(move || {
+                // Create a vector of successful entries
+                let successful_entries = vec![successful.clone()];
+
+                // Create an empty vector for failed entries
+                let failed_entries: Vec<BatchResultErrorEntry> = Vec::new();
+
+                aws_sdk_sqs::operation::send_message_batch::SendMessageBatchOutput::builder()
+                    .set_successful(Some(successful_entries))
+                    .set_failed(Some(failed_entries))
+                    .build()
+                    .unwrap()
+            });
+        send_message_output
+    }
+
+    fn get_send_message_output_with_failed() -> Rule {
+        let successful = aws_sdk_sqs::types::SendMessageBatchResultEntry::builder()
+            .id("1")
+            .message_id("msg-id-1")
+            .md5_of_message_body("84769b6348524b3317694d80c0ac6df9")
+            .build()
+            .unwrap();
+
+        let failed = aws_sdk_sqs::types::BatchResultErrorEntry::builder()
+            .id("2")
+            .code("InvalidParameterValue")
+            .message("The message is too large for the queue.")
+            .sender_fault(true)
+            .build()
+            .unwrap();
+
+        let send_message_output = mock!(aws_sdk_sqs::Client::send_message_batch)
+            .match_requests(|inp| {
+                inp.queue_url().unwrap()
+                    == "https://sqs.us-west-2.amazonaws.com/926113353675/test-q/"
+            })
+            .then_output(move || {
+                // Create a vector of successful entries
+                let successful_entries = vec![successful.clone()];
+
+                // Create an empty vector for failed entries
+                let failed_entries: Vec<BatchResultErrorEntry> = vec![failed.clone()];
+
+                aws_sdk_sqs::operation::send_message_batch::SendMessageBatchOutput::builder()
+                    .set_successful(Some(successful_entries))
+                    .set_failed(Some(failed_entries))
+                    .build()
+                    .unwrap()
+            });
+        send_message_output
+    }
+
+    fn get_send_message_output_all_fail() -> Rule {
+        let send_message_output = mock!(aws_sdk_sqs::Client::send_message_batch)
+            .match_requests(|inp| {
+                inp.queue_url().unwrap()
+                    == "https://sqs.us-west-2.amazonaws.com/926113353675/test-q/"
+            })
+            .then_error(|| {
+                aws_sdk_sqs::operation::send_message_batch::SendMessageBatchError::generic(
+                    ErrorMetadata::builder()
+                        .message("The message is too large for the queue.")
+                        .code("InvalidParameterValue")
+                        .build(),
+                )
+            });
+        send_message_output
+    }
+
+    fn get_test_config_with_interceptor(interceptor: MockResponseInterceptor) -> Config {
+        aws_sdk_sqs::Config::builder()
+            .behavior_version(BehaviorVersion::v2025_01_17())
+            .credentials_provider(make_sqs_test_credentials())
+            .region(aws_sdk_sqs::config::Region::new(SQS_DEFAULT_REGION))
+            .interceptor(interceptor)
+            .build()
+    }
+
+    fn make_sqs_test_credentials() -> aws_sdk_sqs::config::Credentials {
+        aws_sdk_sqs::config::Credentials::new(
+            "ATESTCLIENT",
+            "astestsecretkey",
+            Some("atestsessiontoken".to_string()),
+            None,
+            "",
+        )
+    }
+}
diff --git a/rust/extns/numaflow-sqs/src/source.rs b/rust/extns/numaflow-sqs/src/source.rs
index 8feeeddb..12ce0db3 100644
--- a/rust/extns/numaflow-sqs/src/source.rs
+++ b/rust/extns/numaflow-sqs/src/source.rs
@@ -10,10 +10,6 @@ use std::collections::HashMap;
 use std::str::FromStr;
 use std::time::Duration;
 
-use crate::Error::ActorTaskTerminated;
-use crate::{Error, Result};
-use aws_config::meta::region::RegionProviderChain;
-use aws_config::{BehaviorVersion, Region};
 use aws_sdk_sqs::Client;
 use aws_sdk_sqs::types::{MessageSystemAttributeName, QueueAttributeName};
 use bytes::Bytes;
@@ -21,17 +17,22 @@ use chrono::{DateTime, TimeZone, Utc};
 use tokio::sync::{mpsc, oneshot};
 use tokio::time::Instant;
 
+use crate::Error::ActorTaskTerminated;
+use crate::{Error, SqsConfig, SqsSourceError};
+
 pub const SQS_DEFAULT_REGION: &str = "us-west-2";
 
+pub type Result<T> = std::result::Result<T, SqsSourceError>;
+
 /// Configuration for an SQS message source.
 ///
 /// Used to initialize the SQS client with region and queue settings.
 #[derive(Debug, Clone, PartialEq)]
-pub struct SQSSourceConfig {
+pub struct SqsSourceConfig {
     // Required fields
-    pub region: String,
-    pub queue_name: String,
-    pub queue_owner_aws_account_id: String,
+    pub region: &'static str,
+    pub queue_name: &'static str,
+    pub queue_owner_aws_account_id: &'static str,
 
     // Optional fields
     pub visibility_timeout: Option<i32>,
@@ -42,92 +43,6 @@ pub struct SQSSourceConfig {
     pub message_attribute_names: Vec<String>,
 }
 
-impl SQSSourceConfig {
-    /// Validates the SQS source configuration
-    #[allow(clippy::result_large_err)]
-    pub fn validate(&self) -> Result<()> {
-        // Validate required fields
-        if self.region.is_empty() {
-            return Err(Error::InvalidConfig("region is required".to_string()));
-        }
-
-        if self.queue_name.is_empty() {
-            return Err(Error::InvalidConfig("queue name is required".to_string()));
-        }
-
-        // Validate optional fields if present
-        if let Some(timeout) = self.visibility_timeout {
-            if !(0..=43200).contains(&timeout) {
-                return Err(Error::InvalidConfig(format!(
-                    "visibility_timeout must be between 0 and 43200, got {}",
-                    timeout
-                )));
-            }
-        }
-
-        if let Some(max_msgs) = self.max_number_of_messages {
-            if !(1..=10).contains(&max_msgs) {
-                return Err(Error::InvalidConfig(format!(
-                    "max_number_of_messages must be between 1 and 10, got {}",
-                    max_msgs
-                )));
-            }
-        }
-
-        if let Some(wait_time) = self.wait_time_seconds {
-            if !(0..=20).contains(&wait_time) {
-                return Err(Error::InvalidConfig(format!(
-                    "wait_time_seconds must be between 0 and 20, got {}",
-                    wait_time
-                )));
-            }
-        }
-
-        Ok(())
-    }
-}
-
-/// Creates and configures an SQS client based on the provided configuration.
-pub async fn create_sqs_client(config: Option<SQSSourceConfig>) -> Result<Client> {
-    let config = match config {
-        Some(cfg) => cfg,
-        None => {
-            return Err(Error::InvalidConfig(
-                "SQS configuration is required".to_string(),
-            ));
-        }
-    };
-
-    // Validate configuration before proceeding
-    config.validate()?;
-
-    tracing::info!(
-        "Creating SQS client for queue {queue_name} in region {region}",
-        region = config.region.clone(),
-        queue_name = config.queue_name.clone()
-    );
-
-    let region_provider = RegionProviderChain::first_try(Region::new(config.region.clone()))
-        .or_default_provider()
-        .or_else(Region::new("us-west-2")); // Default region if none provided
-
-    // recommended to pin behavior version
-    // https://docs.aws.amazon.com/sdk-for-rust/latest/dg/behavior-versions.html
-    let mut config_builder =
-        aws_config::defaults(BehaviorVersion::v2025_01_17()).region(region_provider);
-
-    // Apply endpoint URL if configured
-    if let Some(endpoint_url) = config.endpoint_url {
-        config_builder = config_builder.endpoint_url(endpoint_url);
-    }
-
-    // Load the shared config
-    let shared_config = config_builder.load().await;
-
-    // Create and return the client
-    Ok(Client::new(&shared_config))
-}
-
 /// Internal message types for the actor implementation.
 ///
 /// The actor pattern is used to:
@@ -136,7 +51,7 @@ pub async fn create_sqs_client(config: Option<SQSSourceConfig>) -> Result<Client
 /// - Handle concurrent requests without locks
 enum SQSActorMessage {
     Receive {
-        respond_to: oneshot::Sender<Result<Vec<SQSMessage>>>,
+        respond_to: oneshot::Sender<Result<Vec<SqsMessage>>>,
         count: i32,
         timeout_at: Instant,
     },
@@ -156,7 +71,7 @@ enum SQSActorMessage {
 /// - Includes full message metadata for debugging
 /// - Maintains original SQS attributes and headers
 #[derive(Debug)]
-pub struct SQSMessage {
+pub struct SqsMessage {
     pub key: String,
     pub payload: Bytes,
     pub offset: String,
@@ -174,7 +89,7 @@ struct SqsActor {
     handler_rx: mpsc::Receiver<SQSActorMessage>,
     client: Client,
     queue_url: String,
-    config: SQSSourceConfig,
+    config: SqsSourceConfig,
 }
 
 impl SqsActor {
@@ -182,7 +97,7 @@ impl SqsActor {
         handler_rx: mpsc::Receiver<SQSActorMessage>,
         client: Client,
         queue_url: String,
-        config: SQSSourceConfig,
+        config: SqsSourceConfig,
     ) -> Self {
         Self {
             handler_rx,
@@ -234,7 +149,7 @@ impl SqsActor {
     /// - Respects timeout for long polling
     /// - Processes message attributes and system metadata
     /// - Returns messages in a normalized format
-    async fn get_messages(&mut self, count: i32, timeout_at: Instant) -> Result<Vec<SQSMessage>> {
+    async fn get_messages(&mut self, count: i32, timeout_at: Instant) -> Result<Vec<SqsMessage>> {
         let remaining_time = timeout_at - Instant::now();
 
         // default to one second if remaining time is less than one second
@@ -302,7 +217,7 @@ impl SqsActor {
                     queue_url = self.queue_url,
                     "failed to receive messages from SQS"
                 );
-                return Err(Error::Sqs(err.into()));
+                return Err(SqsSourceError::from(Error::Sqs(err.into())));
             }
         };
 
@@ -343,7 +258,7 @@ impl SqsActor {
                     })
                     .unwrap_or_default();
 
-                SQSMessage {
+                SqsMessage {
                     key,
                     payload,
                     offset,
@@ -364,7 +279,9 @@ impl SqsActor {
                 Ok(offset) => offset,
                 Err(err) => {
                     tracing::error!(?err, "failed to parse offset");
-                    return Err(Error::Other("failed to parse offset".to_string()));
+                    return Err(SqsSourceError::from(Error::Other(
+                        "failed to parse offset".to_string(),
+                    )));
                 }
             };
             if let Err(err) = self
@@ -380,7 +297,7 @@ impl SqsActor {
                     queue_url = ?self.queue_url,
                     "Error while deleting message from SQS"
                 );
-                return Err(Error::Sqs(err.into()));
+                return Err(SqsSourceError::from(Error::Sqs(err.into())));
             }
         }
         Ok(())
@@ -407,7 +324,7 @@ impl SqsActor {
                     queue_url = ?self.queue_url,
                     "failed to get queue attributes from SQS"
                 );
-                return Err(Error::Sqs(err.into()));
+                return Err(SqsSourceError::from(Error::Sqs(err.into())));
             }
         };
 
@@ -425,9 +342,9 @@ impl SqsActor {
             Ok(count) => count,
             Err(err) => {
                 tracing::error!(?err, "failed to parse ApproximateNumberOfMessages");
-                return Err(Error::Other(
+                return Err(SqsSourceError::from(Error::Other(
                     "failed to parse ApproximateNumberOfMessages".to_string(),
-                ));
+                )));
             }
         };
         Ok(Some(approx_pending_messages_count))
@@ -442,7 +359,7 @@ impl SqsActor {
 /// - Clean abstraction of SQS complexity
 /// - Efficient message processing
 #[derive(Clone)]
-pub struct SQSSource {
+pub struct SqsSource {
     batch_size: usize,
     /// timeout for each batch read request
     timeout: Duration,
@@ -456,7 +373,7 @@ pub struct SQSSource {
 /// such as region, queue name, batch size, timeout, and an optional SQS client.
 #[derive(Clone)]
 pub struct SqsSourceBuilder {
-    config: SQSSourceConfig,
+    config: SqsSourceConfig,
     batch_size: usize,
     timeout: Duration,
     client: Option<Client>,
@@ -465,10 +382,10 @@ pub struct SqsSourceBuilder {
 
 impl Default for SqsSourceBuilder {
     fn default() -> Self {
-        Self::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "".to_string(),
-            queue_owner_aws_account_id: "".to_string(),
+        Self::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "",
+            queue_owner_aws_account_id: "",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -480,7 +397,7 @@ impl Default for SqsSourceBuilder {
 }
 
 impl SqsSourceBuilder {
-    pub fn new(config: SQSSourceConfig) -> Self {
+    pub fn new(config: SqsSourceConfig) -> Self {
         Self {
             config,
             batch_size: 1,
@@ -489,7 +406,7 @@ impl SqsSourceBuilder {
             vertex_replica: 0,
         }
     }
-    pub fn config(mut self, config: SQSSourceConfig) -> Self {
+    pub fn config(mut self, config: SqsSourceConfig) -> Self {
         self.config = config;
         self
     }
@@ -523,17 +440,14 @@ impl SqsSourceBuilder {
     /// # Returns
     /// - `Ok(SqsSource)` if the source is successfully built.
     /// - `Err(Error)` if there is an error during the initialization process.
-    pub async fn build(self) -> Result<SQSSource> {
-        // Validate the configuration
-        self.config.validate()?;
-
+    pub async fn build(self) -> Result<SqsSource> {
         let sqs_client = match self.client {
             Some(client) => client,
-            None => create_sqs_client(Some(self.config.clone())).await?,
+            None => crate::create_sqs_client(SqsConfig::Source(self.config.clone())).await?,
         };
 
-        let queue_name = self.config.queue_name.clone();
-        let queue_owner_aws_account_id = self.config.queue_owner_aws_account_id.clone();
+        let queue_name = self.config.queue_name;
+        let queue_owner_aws_account_id = self.config.queue_owner_aws_account_id;
 
         let get_queue_url_output = sqs_client
             .get_queue_url()
@@ -555,7 +469,7 @@ impl SqsSourceBuilder {
             actor.run().await;
         });
 
-        Ok(SQSSource {
+        Ok(SqsSource {
             batch_size: self.batch_size,
             timeout: self.timeout,
             actor_tx: handler_tx,
@@ -564,9 +478,9 @@ impl SqsSourceBuilder {
     }
 }
 
-impl SQSSource {
+impl SqsSource {
     /// read messages from SQS, corresponding sqs sdk method is receive_message
-    pub async fn read_messages(&self) -> Result<Vec<SQSMessage>> {
+    pub async fn read_messages(&self) -> Result<Vec<SqsMessage>> {
         tracing::debug!("Reading messages from SQS");
         let start = Instant::now();
         let (tx, rx) = oneshot::channel();
@@ -643,10 +557,10 @@ mod tests {
 
     #[tokio::test]
     async fn test_client_creation_with_defaults() {
-        let config = SQSSourceConfig {
-            region: "us-west-2".to_string(),
-            queue_name: "test-queue".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let config = SqsSourceConfig {
+            region: "us-west-2",
+            queue_name: "test-queue",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -655,16 +569,16 @@ mod tests {
             message_attribute_names: vec![],
         };
 
-        let result = create_sqs_client(Some(config)).await;
+        let result = crate::create_sqs_client(SqsConfig::Source(config.clone())).await;
         assert!(result.is_ok());
     }
 
     #[tokio::test]
     async fn test_client_creation_with_custom_endpoint() {
-        let mut config = SQSSourceConfig {
-            region: "us-west-2".to_string(),
-            queue_name: "test-queue".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let mut config = SqsSourceConfig {
+            region: "us-west-2",
+            queue_name: "test-queue",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: Some(30),
             max_number_of_messages: Some(5),
             wait_time_seconds: Some(10),
@@ -673,100 +587,15 @@ mod tests {
             message_attribute_names: vec!["All".to_string()],
         };
 
-        let result = create_sqs_client(Some(config.clone())).await;
+        let result = crate::create_sqs_client(SqsConfig::Source(config.clone())).await;
         assert!(result.is_ok());
 
         // Test with invalid endpoint
         config.endpoint_url = Some("invalid-url".to_string());
-        let result = create_sqs_client(Some(config)).await;
+        let result = crate::create_sqs_client(SqsConfig::Source(config)).await;
         assert!(result.is_ok()); // The URL is validated when making requests, not during client creation
     }
 
-    #[tokio::test]
-    async fn test_client_creation_validation_failures() {
-        // Test missing config
-        let result = create_sqs_client(None).await;
-        assert!(matches!(result, Err(Error::InvalidConfig(_))));
-
-        // Test empty region
-        let config = SQSSourceConfig {
-            region: "".to_string(),
-            queue_name: "test-queue".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
-            visibility_timeout: None,
-            max_number_of_messages: None,
-            wait_time_seconds: None,
-            endpoint_url: None,
-            attribute_names: vec![],
-            message_attribute_names: vec![],
-        };
-        let result = create_sqs_client(Some(config)).await;
-        assert!(matches!(result, Err(Error::InvalidConfig(_))));
-
-        // Test empty queue name
-        let config = SQSSourceConfig {
-            region: "us-west-2".to_string(),
-            queue_name: "".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
-            visibility_timeout: None,
-            max_number_of_messages: None,
-            wait_time_seconds: None,
-            endpoint_url: None,
-            attribute_names: vec![],
-            message_attribute_names: vec![],
-        };
-        let result = create_sqs_client(Some(config)).await;
-        assert!(matches!(result, Err(Error::InvalidConfig(_))));
-
-        // Test invalid max_number_of_messages
-        let config = SQSSourceConfig {
-            region: "us-west-2".to_string(),
-            queue_name: "test-queue".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
-            visibility_timeout: None,
-            max_number_of_messages: Some(20), // Invalid: > 10
-            wait_time_seconds: None,
-            endpoint_url: None,
-            attribute_names: vec![],
-            message_attribute_names: vec![],
-        };
-        let result = create_sqs_client(Some(config)).await;
-        assert!(matches!(result, Err(Error::InvalidConfig(_))));
-
-        // Test invalid wait_time_seconds
-        let config = SQSSourceConfig {
-            region: "us-west-2".to_string(),
-            queue_name: "test-queue".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
-            visibility_timeout: None,
-            max_number_of_messages: None,
-            wait_time_seconds: Some(30), // Invalid: > 20
-            endpoint_url: None,
-            attribute_names: vec![],
-            message_attribute_names: vec![],
-        };
-        let result = create_sqs_client(Some(config)).await;
-        assert!(matches!(result, Err(Error::InvalidConfig(_))));
-    }
-
-    #[tokio::test]
-    async fn test_client_creation_with_invalid_parameters() {
-        let config = SQSSourceConfig {
-            region: "us-west-2".to_string(),
-            queue_name: "test-queue".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
-            visibility_timeout: Some(50000),  // Invalid: > 43200
-            max_number_of_messages: Some(20), // Invalid: > 10
-            wait_time_seconds: Some(30),      // Invalid: > 20
-            endpoint_url: None,
-            attribute_names: vec![],
-            message_attribute_names: vec![],
-        };
-
-        let result = create_sqs_client(Some(config)).await;
-        assert!(matches!(result, Err(Error::InvalidConfig(_))));
-    }
-
     #[test(tokio::test)]
     async fn test_sqssourcehandle_read() {
         let queue_url_output = get_queue_url_output();
@@ -781,10 +610,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: Some(300),
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -823,10 +652,10 @@ mod tests {
             .with_rule(&receive_message_output);
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: Some(300),
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -862,10 +691,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: Some(300),
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -888,7 +717,7 @@ mod tests {
             Err(err) => {
                 assert_eq!(
                     err.to_string(),
-                    "Failed with SQS error - unhandled error (InvalidAddress)"
+                    "SQS Source Error: Failed with SQS error - unhandled error (InvalidAddress)"
                 );
             }
         }
@@ -907,10 +736,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -944,10 +773,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -981,10 +810,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -1016,10 +845,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -1048,10 +877,10 @@ mod tests {
         let sqs_mock_client =
             Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -1069,7 +898,7 @@ mod tests {
 
     #[tokio::test]
     async fn test_partitions_unimplemented() {
-        let source = SQSSource {
+        let source = SqsSource {
             batch_size: 1,
             timeout: Duration::from_secs(0),
             actor_tx: mpsc::channel(1).0,
@@ -1090,10 +919,10 @@ mod tests {
         assert_eq!(builder.vertex_replica, 2);
 
         // test with custom config
-        let config = SQSSourceConfig {
-            region: "us-east-2".to_string(),
-            queue_name: "test-queue-custom".to_string(),
-            queue_owner_aws_account_id: "123456789012".to_string(),
+        let config = SqsSourceConfig {
+            region: "us-east-2",
+            queue_name: "test-queue-custom",
+            queue_owner_aws_account_id: "123456789012",
             visibility_timeout: Some(300),
             max_number_of_messages: Some(2000),
             wait_time_seconds: Some(10),
diff --git a/rust/numaflow-core/Cargo.toml b/rust/numaflow-core/Cargo.toml
index 4ba2eab2..236cae05 100644
--- a/rust/numaflow-core/Cargo.toml
+++ b/rust/numaflow-core/Cargo.toml
@@ -7,7 +7,8 @@ edition = "2024"
 nats-tests = []
 pulsar-tests = []
 sqs-tests = []
-all-tests = ["nats-tests", "pulsar-tests", "sqs-tests"]
+kafka-tests = []
+all-tests = ["nats-tests", "pulsar-tests", "sqs-tests", "kafka-tests"]
 
 [lints]
 workspace = true
@@ -18,6 +19,7 @@ tracing.workspace = true
 numaflow-pulsar.workspace = true
 numaflow-monitor.workspace = true
 numaflow-jetstream.workspace = true
+numaflow-kafka.workspace = true
 numaflow-sqs.workspace = true
 numaflow-models.workspace = true
 numaflow-pb.workspace = true
@@ -30,10 +32,11 @@ serde.workspace = true
 rustls.workspace = true
 tower.workspace = true
 async-nats.workspace = true
+tokio-stream.workspace = true
+futures.workspace = true
 tonic = { version = "0.13.0" }
 thiserror = "2.0.3"
 tokio-util = "0.7.11"
-tokio-stream = "0.1.15"
 prost = "0.13.2"
 prost-types = "0.13.1"
 chrono = "0.4.31"
@@ -43,11 +46,10 @@ serde_json = "1.0.122"
 trait-variant = "0.1.2"
 rcgen = "0.13.1"
 semver = "1.0"
-pep440_rs = "0.6.6"
+pep440_rs = "0.7.3"
 parking_lot = "0.12.3"
-prometheus-client = "0.22.3"
+prometheus-client = "0.23.1"
 kube = "0.99.0"
-futures = "0.3.30"
 pin-project = "1.1.5"
 rand = "0.8.5"
 
@@ -61,5 +63,7 @@ http = "1.2.0"
 aws-sdk-sqs = "1.65.0"
 aws-smithy-mocks-experimental = "0.2.3"
 k8s-openapi = { version = "0.24.0", features = ["v1_32"] }
+numaflow-kafka = { workspace = true, features = ["kafka-tests-utils"] }
+
 
 [build-dependencies]
diff --git a/rust/numaflow-core/src/config.rs b/rust/numaflow-core/src/config.rs
index ae8352e6..e9aec532 100644
--- a/rust/numaflow-core/src/config.rs
+++ b/rust/numaflow-core/src/config.rs
@@ -258,10 +258,33 @@ mod tests {
                 mvtx_cfg
                     .sink_config
                     .retry_config
+                    .clone()
                     .unwrap()
-                    .sink_retry_interval_in_ms,
+                    .sink_initial_retry_interval_in_ms,
                 1000
             );
+            assert_eq!(
+                mvtx_cfg
+                    .sink_config
+                    .retry_config
+                    .clone()
+                    .unwrap()
+                    .sink_max_retry_interval_in_ms,
+                4294967295
+            );
+            assert_eq!(
+                mvtx_cfg
+                    .sink_config
+                    .retry_config
+                    .clone()
+                    .unwrap()
+                    .sink_retry_factor,
+                1.0
+            );
+            assert_eq!(
+                mvtx_cfg.sink_config.retry_config.unwrap().sink_retry_jitter,
+                0.0
+            );
         }
 
         {
@@ -292,7 +315,10 @@ mod tests {
                         "retryStrategy": {
                             "backoff": {
                                 "interval": "1s",
-                                "steps": 5
+                                "steps": 5,
+                                "cap": "2s",
+                                "jitter": 0.1,
+                                "factor": 1.5
                             },
                             "onFailure": "drop"
                         },
@@ -340,9 +366,31 @@ mod tests {
                     .retry_config
                     .clone()
                     .unwrap()
-                    .sink_retry_interval_in_ms,
+                    .sink_initial_retry_interval_in_ms,
                 1000
             );
+            assert_eq!(
+                mvtx_cfg
+                    .sink_config
+                    .retry_config
+                    .clone()
+                    .unwrap()
+                    .sink_max_retry_interval_in_ms,
+                2000
+            );
+            assert_eq!(
+                mvtx_cfg
+                    .sink_config
+                    .retry_config
+                    .clone()
+                    .unwrap()
+                    .sink_retry_factor,
+                1.5
+            );
+            assert_eq!(
+                mvtx_cfg.sink_config.retry_config.unwrap().sink_retry_jitter,
+                0.1
+            );
         }
         {
             // Test Invalid on failure strategy to use default
@@ -372,7 +420,10 @@ mod tests {
                         "retryStrategy": {
                             "backoff": {
                                 "interval": "1s",
-                                "steps": 5
+                                "steps": 5,
+                                "cap": "10s",
+                                "jitter": 0.05,
+                                "factor": 2.5
                             },
                             "onFailure": "xxxxx"
                         },
@@ -419,9 +470,35 @@ mod tests {
                     .retry_config
                     .clone()
                     .unwrap()
-                    .sink_retry_interval_in_ms,
+                    .sink_initial_retry_interval_in_ms,
                 1000
             );
+            assert_eq!(
+                mvtx_config
+                    .sink_config
+                    .retry_config
+                    .clone()
+                    .unwrap()
+                    .sink_max_retry_interval_in_ms,
+                10000
+            );
+            assert_eq!(
+                mvtx_config
+                    .sink_config
+                    .retry_config
+                    .clone()
+                    .unwrap()
+                    .sink_retry_factor,
+                2.5
+            );
+            assert_eq!(
+                mvtx_config
+                    .sink_config
+                    .retry_config
+                    .unwrap()
+                    .sink_retry_jitter,
+                0.05
+            );
         }
     }
 }
diff --git a/rust/numaflow-core/src/config/components.rs b/rust/numaflow-core/src/config/components.rs
index ec92e2f8..7e606d5a 100644
--- a/rust/numaflow-core/src/config/components.rs
+++ b/rust/numaflow-core/src/config/components.rs
@@ -1,3 +1,8 @@
+use numaflow_kafka::TlsConfig;
+use numaflow_models::models::{Sasl, Tls};
+
+use crate::Error;
+
 pub(crate) mod source {
     const DEFAULT_GRPC_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024; // 64 MB
     const DEFAULT_SOURCE_SOCKET: &str = "/var/run/numaflow/source.sock";
@@ -7,14 +12,17 @@ pub(crate) mod source {
 
     use bytes::Bytes;
     use numaflow_jetstream::{JetstreamSourceConfig, NatsAuth, TlsClientAuthCerts, TlsConfig};
+    use numaflow_kafka::source::KafkaSourceConfig;
     use numaflow_models::models::{GeneratorSource, PulsarSource, Source, SqsSource};
     use numaflow_pulsar::source::{PulsarAuth, PulsarSourceConfig};
-    use numaflow_sqs::source::SQSSourceConfig;
-    use tracing::{info, warn};
+    use numaflow_sqs::source::SqsSourceConfig;
+    use tracing::warn;
 
     use crate::Result;
     use crate::error::Error;
 
+    use super::parse_kafka_auth_config;
+
     #[derive(Debug, Clone, PartialEq)]
     pub(crate) struct SourceConfig {
         /// for high-throughput use-cases we read-ahead the next batch before the previous batch has
@@ -38,7 +46,8 @@ pub(crate) mod source {
         UserDefined(UserDefinedConfig),
         Pulsar(PulsarSourceConfig),
         Jetstream(JetstreamSourceConfig),
-        Sqs(SQSSourceConfig),
+        Sqs(SqsSourceConfig),
+        Kafka(Box<KafkaSourceConfig>),
     }
 
     impl From<Box<GeneratorSource>> for SourceType {
@@ -80,7 +89,7 @@ pub(crate) mod source {
             let auth: Option<PulsarAuth> = match value.auth {
                 Some(auth) => 'out: {
                     let Some(token) = auth.token else {
-                        tracing::warn!("JWT Token authentication is specified, but token is empty");
+                        warn!("JWT Token authentication is specified, but token is empty");
                         break 'out None;
                     };
                     let secret = crate::shared::create_components::get_secret_from_volume(
@@ -108,17 +117,57 @@ pub(crate) mod source {
         type Error = Error;
 
         fn try_from(value: Box<SqsSource>) -> Result<Self> {
-            info!("Sqs source: {value:?}");
             if value.aws_region.is_empty() {
                 return Err(Error::Config(
-                    "AWS region is required for SQS source".to_string(),
+                    "aws_region is required for SQS source".to_string(),
+                ));
+            }
+
+            if value.queue_name.is_empty() {
+                return Err(Error::Config(
+                    "queue_name is required for SQS source".to_string(),
+                ));
+            }
+
+            if value.queue_owner_aws_account_id.is_empty() {
+                return Err(Error::Config(
+                    "queue_owner_aws_account_id is required for SQS source".to_string(),
                 ));
             }
 
-            let sqs_source_config = SQSSourceConfig {
-                queue_name: value.queue_name,
-                region: value.aws_region,
-                queue_owner_aws_account_id: value.queue_owner_aws_account_id,
+            if let Some(timeout) = value.visibility_timeout {
+                if !(0..=43200).contains(&timeout) {
+                    return Err(Error::Config(format!(
+                        "visibility_timeout must be between 0 and 43200 for SQS source, got {}",
+                        timeout
+                    )));
+                }
+            }
+
+            if let Some(wait_time) = value.wait_time_seconds {
+                if !(0..=20).contains(&wait_time) {
+                    return Err(Error::Config(format!(
+                        "wait_time_seconds must be between 0 and 20 for SQS source, got {}",
+                        wait_time
+                    )));
+                }
+            }
+
+            if let Some(max_number_of_messages) = value.max_number_of_messages {
+                if !(1..=10).contains(&max_number_of_messages) {
+                    return Err(Error::Config(format!(
+                        "max_number_of_messages must be between 1 and 10 for SQS source, got {}",
+                        max_number_of_messages
+                    )));
+                }
+            }
+
+            let sqs_source_config = SqsSourceConfig {
+                queue_name: Box::leak(value.queue_name.into_boxed_str()),
+                region: Box::leak(value.aws_region.into_boxed_str()),
+                queue_owner_aws_account_id: Box::leak(
+                    value.queue_owner_aws_account_id.into_boxed_str(),
+                ),
                 attribute_names: value.attribute_names.unwrap_or_default(),
                 message_attribute_names: value.message_attribute_names.unwrap_or_default(),
                 max_number_of_messages: Some(value.max_number_of_messages.unwrap_or(10)),
@@ -127,8 +176,6 @@ pub(crate) mod source {
                 endpoint_url: value.endpoint_url,
             };
 
-            info!("parsed SQS source config: {sqs_source_config:?}");
-
             Ok(SourceType::Sqs(sqs_source_config))
         }
     }
@@ -266,6 +313,28 @@ pub(crate) mod source {
         }
     }
 
+    impl TryFrom<Box<numaflow_models::models::KafkaSource>> for SourceType {
+        type Error = Error;
+        fn try_from(
+            value: Box<numaflow_models::models::KafkaSource>,
+        ) -> std::result::Result<Self, Self::Error> {
+            let (auth, tls) = parse_kafka_auth_config(value.sasl.clone(), value.tls.clone())?;
+
+            let kafka_config = numaflow_kafka::source::KafkaSourceConfig {
+                brokers: value.brokers.unwrap_or_default(),
+                topics: value
+                    .topic
+                    .split(',')
+                    .map(|s| s.trim().to_string())
+                    .collect(),
+                consumer_group: value.consumer_group.unwrap_or_default(),
+                auth,
+                tls,
+            };
+            Ok(SourceType::Kafka(Box::new(kafka_config)))
+        }
+    }
+
     impl TryFrom<Box<Source>> for SourceType {
         type Error = Error;
 
@@ -294,6 +363,10 @@ pub(crate) mod source {
                 return jetstream.try_into();
             }
 
+            if let Some(kafka) = source.kafka.take() {
+                return kafka.try_into();
+            }
+
             Err(Error::Config(format!("Invalid source type: {source:?}")))
         }
     }
@@ -349,15 +422,22 @@ pub(crate) mod sink {
     const DEFAULT_FB_SINK_SERVER_INFO_FILE: &str = "/var/run/numaflow/fb-sinker-server-info";
     const DEFAULT_SINK_RETRY_ON_FAIL_STRATEGY: OnFailureStrategy = OnFailureStrategy::Retry;
     const DEFAULT_MAX_SINK_RETRY_ATTEMPTS: u16 = u16::MAX;
-    const DEFAULT_SINK_RETRY_INTERVAL_IN_MS: u32 = 1;
+    const DEFAULT_SINK_INITIAL_RETRY_INTERVAL_IN_MS: u32 = 1;
+    const DEFAULT_SINK_MAX_RETRY_INTERVAL_IN_MS: u32 = u32::MAX;
+    const DEFAULT_SINK_RETRY_FACTOR: f64 = 1.0;
+    const DEFAULT_SINK_RETRY_JITTER: f64 = 0.0;
 
     use std::fmt::Display;
 
-    use numaflow_models::models::{Backoff, RetryStrategy, Sink};
+    use numaflow_kafka::sink::KafkaSinkConfig;
+    use numaflow_models::models::{Backoff, KafkaSink, RetryStrategy, Sink, SqsSink};
+    use numaflow_sqs::sink::SqsSinkConfig;
 
     use crate::Result;
     use crate::error::Error;
 
+    use super::parse_kafka_auth_config;
+
     #[derive(Debug, Clone, PartialEq)]
     pub(crate) struct SinkConfig {
         pub(crate) sink_type: SinkType,
@@ -370,6 +450,8 @@ pub(crate) mod sink {
         Blackhole(BlackholeConfig),
         Serve,
         UserDefined(UserDefinedConfig),
+        Sqs(SqsSinkConfig),
+        Kafka(Box<KafkaSinkConfig>),
     }
 
     impl SinkType {
@@ -392,6 +474,8 @@ pub(crate) mod sink {
                         .map(|_| Ok(SinkType::Blackhole(BlackholeConfig::default())))
                 })
                 .or_else(|| sink.serve.as_ref().map(|_| Ok(SinkType::Serve)))
+                .or_else(|| sink.sqs.as_ref().map(|sqs| sqs.clone().try_into()))
+                .or_else(|| sink.kafka.as_ref().map(|kafka| kafka.clone().try_into()))
                 .ok_or_else(|| Error::Config("Sink type not found".to_string()))?
         }
 
@@ -414,6 +498,7 @@ pub(crate) mod sink {
                             .map(|_| Ok(SinkType::Blackhole(BlackholeConfig::default())))
                     })
                     .or_else(|| sink.serve.as_ref().map(|_| Ok(SinkType::Serve)))
+                    .or_else(|| fallback.sqs.as_ref().map(|sqs| sqs.clone().try_into()))
                     .ok_or_else(|| Error::Config("Sink type not found".to_string()))?
             } else {
                 Err(Error::Config("Fallback sink not found".to_string()))
@@ -434,6 +519,67 @@ pub(crate) mod sink {
         pub server_info_path: String,
     }
 
+    impl TryFrom<Box<SqsSink>> for SinkType {
+        type Error = Error;
+
+        fn try_from(value: Box<SqsSink>) -> Result<Self> {
+            if value.aws_region.is_empty() {
+                return Err(Error::Config(
+                    "AWS region is required for SQS sink".to_string(),
+                ));
+            }
+
+            if value.queue_name.is_empty() {
+                return Err(Error::Config(
+                    "Queue name is required for SQS sink".to_string(),
+                ));
+            }
+
+            if value.queue_owner_aws_account_id.is_empty() {
+                return Err(Error::Config(
+                    "Queue owner AWS account ID is required for SQS sink".to_string(),
+                ));
+            }
+
+            let sqs_sink_config = SqsSinkConfig {
+                queue_name: Box::leak(value.queue_name.into_boxed_str()),
+                region: Box::leak(value.aws_region.into_boxed_str()),
+                queue_owner_aws_account_id: Box::leak(
+                    value.queue_owner_aws_account_id.into_boxed_str(),
+                ),
+            };
+            Ok(SinkType::Sqs(sqs_sink_config))
+        }
+    }
+
+    impl TryFrom<Box<KafkaSink>> for SinkType {
+        type Error = Error;
+
+        fn try_from(kafka_config: Box<KafkaSink>) -> Result<Self> {
+            let Some(brokers) = kafka_config.brokers else {
+                return Err(Error::Config(
+                    "Brokers must be specified in the Kafka sink config".to_string(),
+                ));
+            };
+            if brokers.is_empty() {
+                return Err(Error::Config(
+                    "At-least 1 broker URL must be specified in Kafka sink config".to_string(),
+                ));
+            }
+
+            let (auth, tls) =
+                parse_kafka_auth_config(kafka_config.sasl.clone(), kafka_config.tls.clone())?;
+
+            Ok(SinkType::Kafka(Box::new(KafkaSinkConfig {
+                brokers,
+                topic: kafka_config.topic,
+                auth,
+                tls,
+                set_partition_key: kafka_config.set_key.unwrap_or(false),
+            })))
+        }
+    }
+
     #[derive(Debug, Clone, PartialEq)]
     pub(crate) enum OnFailureStrategy {
         Retry,
@@ -474,7 +620,10 @@ pub(crate) mod sink {
     #[derive(Debug, Clone, PartialEq)]
     pub(crate) struct RetryConfig {
         pub sink_max_retry_attempts: u16,
-        pub sink_retry_interval_in_ms: u32,
+        pub sink_initial_retry_interval_in_ms: u32,
+        pub sink_retry_factor: f64,
+        pub sink_retry_jitter: f64,
+        pub sink_max_retry_interval_in_ms: u32,
         pub sink_retry_on_fail_strategy: OnFailureStrategy,
         pub sink_default_retry_strategy: RetryStrategy,
     }
@@ -484,15 +633,27 @@ pub(crate) mod sink {
             let default_retry_strategy = RetryStrategy {
                 backoff: Option::from(Box::from(Backoff {
                     interval: Option::from(kube::core::Duration::from(
-                        std::time::Duration::from_millis(DEFAULT_SINK_RETRY_INTERVAL_IN_MS as u64),
+                        std::time::Duration::from_millis(
+                            DEFAULT_SINK_INITIAL_RETRY_INTERVAL_IN_MS as u64,
+                        ),
                     )),
                     steps: Option::from(DEFAULT_MAX_SINK_RETRY_ATTEMPTS as i64),
+                    cap: Option::from(kube::core::Duration::from(
+                        std::time::Duration::from_millis(
+                            DEFAULT_SINK_MAX_RETRY_INTERVAL_IN_MS as u64,
+                        ),
+                    )),
+                    factor: Option::from(DEFAULT_SINK_RETRY_FACTOR),
+                    jitter: Option::from(DEFAULT_SINK_RETRY_JITTER),
                 })),
                 on_failure: Option::from(DEFAULT_SINK_RETRY_ON_FAIL_STRATEGY.to_string()),
             };
             Self {
                 sink_max_retry_attempts: DEFAULT_MAX_SINK_RETRY_ATTEMPTS,
-                sink_retry_interval_in_ms: DEFAULT_SINK_RETRY_INTERVAL_IN_MS,
+                sink_initial_retry_interval_in_ms: DEFAULT_SINK_INITIAL_RETRY_INTERVAL_IN_MS,
+                sink_max_retry_interval_in_ms: DEFAULT_SINK_MAX_RETRY_INTERVAL_IN_MS,
+                sink_retry_factor: DEFAULT_SINK_RETRY_FACTOR,
+                sink_retry_jitter: DEFAULT_SINK_RETRY_JITTER,
                 sink_retry_on_fail_strategy: DEFAULT_SINK_RETRY_ON_FAIL_STRATEGY,
                 sink_default_retry_strategy: default_retry_strategy,
             }
@@ -504,13 +665,26 @@ pub(crate) mod sink {
             let mut retry_config = RetryConfig::default();
             if let Some(backoff) = &retry.backoff {
                 if let Some(interval) = backoff.interval {
-                    retry_config.sink_retry_interval_in_ms =
+                    retry_config.sink_initial_retry_interval_in_ms =
                         std::time::Duration::from(interval).as_millis() as u32;
                 }
 
                 if let Some(steps) = backoff.steps {
                     retry_config.sink_max_retry_attempts = steps as u16;
                 }
+
+                if let Some(factor) = backoff.factor {
+                    retry_config.sink_retry_factor = factor;
+                }
+
+                if let Some(jitter) = backoff.jitter {
+                    retry_config.sink_retry_jitter = jitter;
+                }
+
+                if let Some(cap) = backoff.cap {
+                    retry_config.sink_max_retry_interval_in_ms =
+                        std::time::Duration::from(cap).as_millis() as u32;
+                }
             }
 
             if let Some(strategy) = &retry.on_failure {
@@ -619,6 +793,451 @@ pub(crate) mod metrics {
     }
 }
 
+pub(crate) mod reduce {
+    const DEFAULT_GRPC_MAX_MESSAGE_SIZE: usize = 64 * 1024 * 1024; // 64 MB
+    const DEFAULT_REDUCER_SOCKET: &str = "/var/run/numaflow/reduce.sock";
+    const DEFAULT_REDUCER_SERVER_INFO_FILE: &str = "/var/run/numaflow/reducer-server-info";
+
+    use std::time::Duration;
+
+    use numaflow_models::models::{
+        AccumulatorWindow, FixedWindow, GroupBy, PbqStorage, SessionWindow, SlidingWindow, Udf,
+    };
+
+    use crate::Result;
+    use crate::error::Error;
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct ReducerConfig {
+        pub(crate) reducer_type: ReducerType,
+        pub(crate) window_config: WindowConfig,
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) enum ReducerType {
+        UserDefined(UserDefinedConfig),
+    }
+
+    impl TryFrom<(&Box<Udf>, &Box<GroupBy>)> for ReducerType {
+        type Error = Error;
+        fn try_from(value: (&Box<Udf>, &Box<GroupBy>)) -> Result<Self> {
+            let (udf, _group_by) = value;
+            if udf.container.is_some() {
+                Ok(ReducerType::UserDefined(UserDefinedConfig::default()))
+            } else {
+                Err(Error::Config("Invalid UDF for reducer".to_string()))
+            }
+        }
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct UserDefinedConfig {
+        pub grpc_max_message_size: usize,
+        pub socket_path: &'static str,
+        pub server_info_path: &'static str,
+    }
+
+    impl Default for UserDefinedConfig {
+        fn default() -> Self {
+            Self {
+                grpc_max_message_size: DEFAULT_GRPC_MAX_MESSAGE_SIZE,
+                socket_path: DEFAULT_REDUCER_SOCKET,
+                server_info_path: DEFAULT_REDUCER_SERVER_INFO_FILE,
+            }
+        }
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct WindowConfig {
+        pub(crate) window_type: WindowType,
+        pub(crate) allowed_lateness: Duration,
+        pub(crate) is_keyed: bool,
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) enum WindowType {
+        Fixed(FixedWindowConfig),
+        Sliding(SlidingWindowConfig),
+        Session(SessionWindowConfig),
+        Accumulator(AccumulatorWindowConfig),
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct FixedWindowConfig {
+        pub(crate) length: Duration,
+    }
+
+    impl From<Box<FixedWindow>> for FixedWindowConfig {
+        fn from(value: Box<FixedWindow>) -> Self {
+            Self {
+                length: value.length.map(Duration::from).unwrap_or_default(),
+            }
+        }
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct SlidingWindowConfig {
+        pub(crate) length: Duration,
+        pub(crate) slide: Duration,
+    }
+
+    impl From<Box<SlidingWindow>> for SlidingWindowConfig {
+        fn from(value: Box<SlidingWindow>) -> Self {
+            Self {
+                length: value.length.map(Duration::from).unwrap_or_default(),
+                slide: value.slide.map(Duration::from).unwrap_or_default(),
+            }
+        }
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct SessionWindowConfig {
+        pub(crate) timeout: Duration,
+    }
+
+    impl From<Box<SessionWindow>> for SessionWindowConfig {
+        fn from(value: Box<SessionWindow>) -> Self {
+            Self {
+                timeout: value.timeout.map(Duration::from).unwrap_or_default(),
+            }
+        }
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct AccumulatorWindowConfig {
+        pub(crate) timeout: Duration,
+    }
+
+    impl From<Box<AccumulatorWindow>> for AccumulatorWindowConfig {
+        fn from(value: Box<AccumulatorWindow>) -> Self {
+            Self {
+                timeout: value.timeout.map(Duration::from).unwrap_or_default(),
+            }
+        }
+    }
+
+    impl TryFrom<&Box<GroupBy>> for WindowConfig {
+        type Error = Error;
+        fn try_from(group_by: &Box<GroupBy>) -> Result<Self> {
+            let window = group_by.window.as_ref();
+
+            let window_type = if let Some(fixed) = &window.fixed {
+                WindowType::Fixed(fixed.clone().into())
+            } else if let Some(sliding) = &window.sliding {
+                WindowType::Sliding(sliding.clone().into())
+            } else if let Some(session) = &window.session {
+                WindowType::Session(session.clone().into())
+            } else if let Some(accumulator) = &window.accumulator {
+                WindowType::Accumulator(accumulator.clone().into())
+            } else {
+                return Err(Error::Config("No window type specified".to_string()));
+            };
+
+            Ok(WindowConfig {
+                window_type,
+                allowed_lateness: group_by
+                    .allowed_lateness
+                    .map_or(Duration::from_secs(0), Duration::from),
+                is_keyed: group_by.keyed.unwrap_or(false),
+            })
+        }
+    }
+
+    #[derive(Debug, Clone, PartialEq)]
+    pub(crate) struct StorageConfig {
+        pub(crate) path: std::path::PathBuf,
+        pub(crate) max_file_size_mb: u64,
+        pub(crate) flush_interval_ms: u64,
+        pub(crate) channel_buffer_size: usize,
+        pub(crate) max_segment_age_secs: u64,
+    }
+
+    impl Default for StorageConfig {
+        fn default() -> Self {
+            Self {
+                path: std::path::PathBuf::from("/var/numaflow/pbq/wals"),
+                max_file_size_mb: 10,
+                flush_interval_ms: 100,
+                channel_buffer_size: 500,
+                max_segment_age_secs: 120,
+            }
+        }
+    }
+
+    impl TryFrom<PbqStorage> for StorageConfig {
+        type Error = crate::error::Error;
+
+        fn try_from(storage: PbqStorage) -> Result<Self> {
+            if storage.persistent_volume_claim.is_some() {
+                Err(Error::Config(
+                    "Persistent volume claim is not supported".to_string(),
+                ))
+            } else {
+                Ok(StorageConfig::default())
+            }
+        }
+    }
+}
+
+fn parse_kafka_auth_config(
+    auth_config: Option<Box<Sasl>>,
+    tls_config: Option<Box<Tls>>,
+) -> crate::Result<(Option<numaflow_kafka::KafkaSaslAuth>, Option<TlsConfig>)> {
+    let auth: Option<numaflow_kafka::KafkaSaslAuth> = match auth_config {
+        Some(sasl) => {
+            let mechanism = sasl.mechanism.to_uppercase();
+            match mechanism.as_str() {
+                "PLAIN" => {
+                    let Some(plain) = sasl.plain else {
+                        return Err(Error::Config(
+                            "PLAIN mechanism requires plain auth configuration".into(),
+                        ));
+                    };
+                    let username = crate::shared::create_components::get_secret_from_volume(
+                        &plain.user_secret.name,
+                        &plain.user_secret.key,
+                    )
+                    .map_err(|e| Error::Config(format!("Failed to get user secret: {e:?}")))?;
+                    let password = if let Some(password_secret) = plain.password_secret {
+                        crate::shared::create_components::get_secret_from_volume(
+                            &password_secret.name,
+                            &password_secret.key,
+                        )
+                        .map_err(|e| {
+                            Error::Config(format!("Failed to get password secret: {e:?}"))
+                        })?
+                    } else {
+                        return Err(Error::Config("PLAIN mechanism requires password".into()));
+                    };
+                    Some(numaflow_kafka::KafkaSaslAuth::Plain { username, password })
+                }
+                "SCRAM-SHA-256" => {
+                    let Some(scram) = sasl.scramsha256 else {
+                        return Err(Error::Config(
+                            "SCRAM-SHA-256 mechanism requires scramsha256 auth configuration"
+                                .into(),
+                        ));
+                    };
+                    let username = crate::shared::create_components::get_secret_from_volume(
+                        &scram.user_secret.name,
+                        &scram.user_secret.key,
+                    )
+                    .map_err(|e| Error::Config(format!("Failed to get user secret: {e:?}")))?;
+                    let password = if let Some(password_secret) = scram.password_secret {
+                        crate::shared::create_components::get_secret_from_volume(
+                            &password_secret.name,
+                            &password_secret.key,
+                        )
+                        .map_err(|e| {
+                            Error::Config(format!("Failed to get password secret: {e:?}"))
+                        })?
+                    } else {
+                        return Err(Error::Config(
+                            "SCRAM-SHA-256 mechanism requires password".into(),
+                        ));
+                    };
+                    Some(numaflow_kafka::KafkaSaslAuth::ScramSha256 { username, password })
+                }
+                "SCRAM-SHA-512" => {
+                    let Some(scram) = sasl.scramsha512 else {
+                        return Err(Error::Config(
+                            "SCRAM-SHA-512 mechanism requires scramsha512 auth configuration"
+                                .into(),
+                        ));
+                    };
+                    let username = crate::shared::create_components::get_secret_from_volume(
+                        &scram.user_secret.name,
+                        &scram.user_secret.key,
+                    )
+                    .map_err(|e| Error::Config(format!("Failed to get user secret: {e:?}")))?;
+                    let password = if let Some(password_secret) = scram.password_secret {
+                        crate::shared::create_components::get_secret_from_volume(
+                            &password_secret.name,
+                            &password_secret.key,
+                        )
+                        .map_err(|e| {
+                            Error::Config(format!("Failed to get password secret: {e:?}"))
+                        })?
+                    } else {
+                        return Err(Error::Config(
+                            "SCRAM-SHA-512 mechanism requires password".into(),
+                        ));
+                    };
+                    Some(numaflow_kafka::KafkaSaslAuth::ScramSha512 { username, password })
+                }
+                "GSSAPI" => {
+                    let Some(gssapi) = sasl.gssapi else {
+                        return Err(Error::Config(
+                            "GSSAPI mechanism requires gssapi configuration".into(),
+                        ));
+                    };
+                    let service_name = gssapi.service_name.clone();
+                    let realm = gssapi.realm.clone();
+                    let username = crate::shared::create_components::get_secret_from_volume(
+                        &gssapi.username_secret.name,
+                        &gssapi.username_secret.key,
+                    )
+                    .map_err(|e| {
+                        Error::Config(format!("Failed to get gssapi username secret: {e:?}"))
+                    })?;
+                    let password = if let Some(password_secret) = gssapi.password_secret {
+                        Some(
+                            crate::shared::create_components::get_secret_from_volume(
+                                &password_secret.name,
+                                &password_secret.key,
+                            )
+                            .map_err(|e| {
+                                Error::Config(format!(
+                                    "Failed to get gssapi password secret: {e:?}"
+                                ))
+                            })?,
+                        )
+                    } else {
+                        None
+                    };
+                    let keytab = if let Some(keytab_secret) = gssapi.keytab_secret {
+                        Some(
+                            crate::shared::create_components::get_secret_from_volume(
+                                &keytab_secret.name,
+                                &keytab_secret.key,
+                            )
+                            .map_err(|e| {
+                                Error::Config(format!("Failed to get gssapi keytab secret: {e:?}"))
+                            })?,
+                        )
+                    } else {
+                        None
+                    };
+                    let kerberos_config =
+                        if let Some(kerberos_config_secret) = gssapi.kerberos_config_secret {
+                            Some(
+                                crate::shared::create_components::get_secret_from_volume(
+                                    &kerberos_config_secret.name,
+                                    &kerberos_config_secret.key,
+                                )
+                                .map_err(|e| {
+                                    Error::Config(format!(
+                                        "Failed to get gssapi kerberos config secret: {e:?}"
+                                    ))
+                                })?,
+                            )
+                        } else {
+                            None
+                        };
+                    let auth_type = format!("{:?}", gssapi.auth_type);
+                    Some(numaflow_kafka::KafkaSaslAuth::Gssapi {
+                        service_name,
+                        realm,
+                        username,
+                        password,
+                        keytab,
+                        kerberos_config,
+                        auth_type,
+                    })
+                }
+                "OAUTH" | "OAUTHBEARER" => {
+                    let Some(oauth) = sasl.oauth else {
+                        return Err(Error::Config(
+                            "OAUTH mechanism requires oauth configuration".into(),
+                        ));
+                    };
+                    let client_id = crate::shared::create_components::get_secret_from_volume(
+                        &oauth.client_id.name,
+                        &oauth.client_id.key,
+                    )
+                    .map_err(|e| Error::Config(format!("Failed to get client id secret: {e:?}")))?;
+                    let client_secret = crate::shared::create_components::get_secret_from_volume(
+                        &oauth.client_secret.name,
+                        &oauth.client_secret.key,
+                    )
+                    .map_err(|e| Error::Config(format!("Failed to get client secret: {e:?}")))?;
+                    let token_endpoint = oauth.token_endpoint.clone();
+                    Some(numaflow_kafka::KafkaSaslAuth::Oauth {
+                        client_id,
+                        client_secret,
+                        token_endpoint,
+                    })
+                }
+                _ => {
+                    return Err(Error::Config(format!(
+                        "Unsupported SASL mechanism: {}",
+                        mechanism
+                    )));
+                }
+            }
+        }
+        None => None,
+    };
+
+    let tls = if let Some(tls_config) = tls_config {
+        let tls_skip_verify = tls_config.insecure_skip_verify.unwrap_or(false);
+        if tls_skip_verify {
+            Some(numaflow_kafka::TlsConfig {
+                insecure_skip_verify: true,
+                ca_cert: None,
+                client_auth: None,
+            })
+        } else {
+            let ca_cert = tls_config
+                .ca_cert_secret
+                .map(
+                    |ca_cert_secret| match crate::shared::create_components::get_secret_from_volume(
+                        &ca_cert_secret.name,
+                        &ca_cert_secret.key,
+                    ) {
+                        Ok(secret) => Ok(secret),
+                        Err(e) => Err(Error::Config(format!(
+                            "Failed to get CA cert secret: {e:?}"
+                        ))),
+                    },
+                )
+                .transpose()?;
+
+            let tls_client_auth_certs = match tls_config.cert_secret {
+                Some(client_cert_secret) => {
+                    let client_cert = crate::shared::create_components::get_secret_from_volume(
+                        &client_cert_secret.name,
+                        &client_cert_secret.key,
+                    )
+                    .map_err(|e| {
+                        Error::Config(format!("Failed to get client cert secret: {e:?}"))
+                    })?;
+
+                    let Some(private_key_secret) = tls_config.key_secret else {
+                        return Err(Error::Config("Client cert is specified for TLS authentication, but private key is not specified".into()));
+                    };
+
+                    let client_cert_private_key =
+                        crate::shared::create_components::get_secret_from_volume(
+                            &private_key_secret.name,
+                            &private_key_secret.key,
+                        )
+                        .map_err(|e| {
+                            Error::Config(format!(
+                                "Failed to get client cert private key secret: {e:?}"
+                            ))
+                        })?;
+                    Some(numaflow_kafka::TlsClientAuthCerts {
+                        client_cert,
+                        client_cert_private_key,
+                    })
+                }
+                None => None,
+            };
+
+            Some(numaflow_kafka::TlsConfig {
+                insecure_skip_verify: tls_config.insecure_skip_verify.unwrap_or(false),
+                ca_cert,
+                client_auth: tls_client_auth_certs,
+            })
+        }
+    } else {
+        None
+    };
+
+    Ok((auth, tls))
+}
+
 #[cfg(test)]
 mod source_tests {
     use std::time::Duration;
@@ -681,12 +1300,12 @@ mod source_tests {
 
 #[cfg(test)]
 mod sink_tests {
-    use numaflow_models::models::{Backoff, RetryStrategy};
-
     use super::sink::{
         BlackholeConfig, LogConfig, OnFailureStrategy, RetryConfig, SinkConfig, SinkType,
         UserDefinedConfig,
     };
+    use numaflow_models::models::{Backoff, RetryStrategy};
+    use numaflow_sqs::sink::SqsSinkConfig;
 
     #[test]
     fn test_default_log_config() {
@@ -719,12 +1338,20 @@ mod sink_tests {
                     std::time::Duration::from_millis(1u64),
                 )),
                 steps: Option::from(u16::MAX as i64),
+                cap: Option::from(kube::core::Duration::from(
+                    std::time::Duration::from_millis(4294967295u64),
+                )),
+                factor: Option::from(1.0),
+                jitter: Option::from(0.0),
             })),
             on_failure: Option::from(OnFailureStrategy::Retry.to_string()),
         };
         let default_config = RetryConfig::default();
         assert_eq!(default_config.sink_max_retry_attempts, u16::MAX);
-        assert_eq!(default_config.sink_retry_interval_in_ms, 1);
+        assert_eq!(default_config.sink_initial_retry_interval_in_ms, 1);
+        assert_eq!(default_config.sink_max_retry_interval_in_ms, 4294967295);
+        assert_eq!(default_config.sink_retry_factor, 1.0);
+        assert_eq!(default_config.sink_retry_jitter, 0.0);
         assert_eq!(
             default_config.sink_retry_on_fail_strategy,
             OnFailureStrategy::Retry
@@ -793,7 +1420,172 @@ mod sink_tests {
             panic!("Expected SinkType::UserDefined");
         }
     }
-}
+
+    #[test]
+    fn test_sink_config_sqs() {
+        let sqs_config = SqsSinkConfig {
+            queue_name: "test-queue",
+            region: "us-west-2",
+            queue_owner_aws_account_id: "123456789012",
+        };
+        let sink_config = SinkConfig {
+            sink_type: SinkType::Sqs(sqs_config.clone()),
+            retry_config: None,
+        };
+        if let SinkType::Sqs(config) = sink_config.sink_type {
+            assert_eq!(config, sqs_config);
+        } else {
+            panic!("Expected SinkType::Sqs");
+        }
+    }
+
+    #[test]
+    fn test_sqs_sink_type_conversion() {
+        use numaflow_models::models::SqsSink;
+
+        // Test case 1: Valid configuration
+        let valid_sqs_sink = Box::new(SqsSink {
+            aws_region: "us-west-2".to_string(),
+            queue_name: "test-queue".to_string(),
+            queue_owner_aws_account_id: "123456789012".to_string(),
+        });
+
+        let result = SinkType::try_from(valid_sqs_sink);
+        assert!(result.is_ok());
+        if let Ok(SinkType::Sqs(config)) = result {
+            assert_eq!(config.region, "us-west-2");
+            assert_eq!(config.queue_name, "test-queue");
+            assert_eq!(config.queue_owner_aws_account_id, "123456789012");
+        } else {
+            panic!("Expected SinkType::Sqs");
+        }
+
+        // Test case 2: Missing required fields
+        let invalid_sqs_sink = Box::new(SqsSink {
+            aws_region: "".to_string(),
+            queue_name: "test-queue".to_string(),
+            queue_owner_aws_account_id: "123456789012".to_string(),
+        });
+
+        let result = SinkType::try_from(invalid_sqs_sink);
+        assert!(result.is_err());
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            "Config Error - AWS region is required for SQS sink"
+        );
+    }
+
+    #[test]
+    fn test_sqs_fallback_sink_type() {
+        use numaflow_models::models::{AbstractSink, Sink, SqsSink};
+
+        // Test case 1: Valid SQS fallback configuration
+        let sink = Sink {
+            udsink: None,
+            log: None,
+            blackhole: None,
+            serve: None,
+            sqs: None,
+            fallback: Some(Box::new(AbstractSink {
+                udsink: None,
+                log: None,
+                blackhole: None,
+                serve: None,
+                sqs: Some(Box::new(SqsSink {
+                    aws_region: "us-west-2".to_string(),
+                    queue_name: "fallback-queue".to_string(),
+                    queue_owner_aws_account_id: "123456789012".to_string(),
+                })),
+                kafka: None,
+            })),
+            retry_strategy: None,
+            kafka: None,
+        };
+
+        let result = SinkType::fallback_sinktype(&sink);
+        assert!(result.is_ok());
+        match result {
+            Ok(SinkType::Sqs(config)) => {
+                assert_eq!(config.region, "us-west-2");
+                assert_eq!(config.queue_name, "fallback-queue");
+                assert_eq!(config.queue_owner_aws_account_id, "123456789012");
+            }
+            _ => panic!("Expected SinkType::Sqs for fallback sink"),
+        }
+
+        // Test case 2: Missing fallback configuration
+        let sink_without_fallback = Sink {
+            udsink: None,
+            log: None,
+            blackhole: None,
+            serve: None,
+            sqs: None,
+            fallback: None,
+            retry_strategy: None,
+            kafka: None,
+        };
+        let result = SinkType::fallback_sinktype(&sink_without_fallback);
+        assert!(result.is_err());
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            "Config Error - Fallback sink not found"
+        );
+
+        // Test case 3: Missing required AWS region
+        let sink_missing_region = Sink {
+            udsink: None,
+            log: None,
+            blackhole: None,
+            serve: None,
+            sqs: None,
+            fallback: Some(Box::new(AbstractSink {
+                udsink: None,
+                log: None,
+                blackhole: None,
+                serve: None,
+                sqs: Some(Box::new(SqsSink {
+                    aws_region: "".to_string(),
+                    queue_name: "fallback-queue".to_string(),
+                    queue_owner_aws_account_id: "123456789012".to_string(),
+                })),
+                kafka: None,
+            })),
+            retry_strategy: None,
+            kafka: None,
+        };
+        let result = SinkType::fallback_sinktype(&sink_missing_region);
+        assert!(result.is_err());
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            "Config Error - AWS region is required for SQS sink"
+        );
+
+        // Test case 4: Empty fallback sink configuration
+        let sink_empty_fallback = Sink {
+            udsink: None,
+            log: None,
+            blackhole: None,
+            serve: None,
+            sqs: None,
+            fallback: Some(Box::new(AbstractSink {
+                udsink: None,
+                log: None,
+                blackhole: None,
+                serve: None,
+                sqs: None,
+                kafka: None,
+            })),
+            retry_strategy: None,
+            kafka: None,
+        };
+        let result = SinkType::fallback_sinktype(&sink_empty_fallback);
+        assert!(result.is_err());
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            "Config Error - Sink type not found"
+        );
+    }
+}
 
 #[cfg(test)]
 mod transformer_tests {
@@ -830,13 +1622,15 @@ mod transformer_tests {
 
 #[cfg(test)]
 mod jetstream_tests {
-    use super::source::SourceType;
+    use std::fs;
+    use std::path::Path;
+
     use k8s_openapi::api::core::v1::SecretKeySelector;
     use numaflow_jetstream::NatsAuth;
     use numaflow_models::models::BasicAuth;
     use numaflow_models::models::{JetStreamSource, Tls};
-    use std::fs;
-    use std::path::Path;
+
+    use super::source::SourceType;
 
     const SECRET_BASE_PATH: &str = "/tmp/numaflow";
 
@@ -901,29 +1695,30 @@ mod jetstream_tests {
 
     #[test]
     fn test_try_from_jetstream_source_with_tls() {
-        let ca_cert_name = "tls-ca-cert";
-        let cert_name = "tls-cert";
-        let key_name = "tls-key";
-        setup_secret(ca_cert_name, "ca", "test-ca-cert");
-        setup_secret(cert_name, "cert", "test-cert");
-        setup_secret(key_name, "key", "test-key");
+        let test_name = "test_try_from_jetstream_source_with_tls";
+        let ca_cert_name = format!("{test_name}-tls-ca-cert");
+        let cert_name = format!("{test_name}-tls-cert");
+        let key_name = format!("{test_name}-tls-key");
+        setup_secret(&ca_cert_name, "ca", "test-ca-cert");
+        setup_secret(&cert_name, "cert", "test-cert");
+        setup_secret(&key_name, "key", "test-key");
 
         let jetstream_source = JetStreamSource {
             auth: None,
             stream: "test-stream".to_string(),
             tls: Some(Box::new(Tls {
                 ca_cert_secret: Some(SecretKeySelector {
-                    name: ca_cert_name.to_string(),
+                    name: ca_cert_name.clone(),
                     key: "ca".to_string(),
                     ..Default::default()
                 }),
                 cert_secret: Some(SecretKeySelector {
-                    name: cert_name.to_string(),
+                    name: cert_name.clone(),
                     key: "cert".to_string(),
                     ..Default::default()
                 }),
                 key_secret: Some(SecretKeySelector {
-                    name: key_name.to_string(),
+                    name: key_name.clone(),
                     key: "key".to_string(),
                     ..Default::default()
                 }),
@@ -936,23 +1731,17 @@ mod jetstream_tests {
         if let SourceType::Jetstream(config) = source_type {
             let tls_config = config.tls.unwrap();
             assert_eq!(tls_config.ca_cert.unwrap(), "test-ca-cert");
-            assert_eq!(
-                tls_config.client_auth.as_ref().unwrap().client_cert,
-                "test-cert"
-            );
-            assert_eq!(
-                tls_config.client_auth.unwrap().client_cert_private_key,
-                "test-key"
-            );
-            assert_eq!(config.consumer, "test-stream");
-            assert_eq!(config.addr, "nats://localhost:4222");
+            let client_auth = tls_config.client_auth.as_ref().unwrap();
+            assert_eq!(client_auth.client_cert, "test-cert");
+            assert_eq!(client_auth.client_cert_private_key, "test-key");
+            assert!(!tls_config.insecure_skip_verify);
         } else {
             panic!("Expected SourceType::Jetstream");
         }
 
-        cleanup_secret(ca_cert_name);
-        cleanup_secret(cert_name);
-        cleanup_secret(key_name);
+        cleanup_secret(&ca_cert_name);
+        cleanup_secret(&cert_name);
+        cleanup_secret(&key_name);
     }
 
     #[test]
@@ -977,3 +1766,802 @@ mod jetstream_tests {
         );
     }
 }
+
+#[cfg(test)]
+mod kafka_tests {
+    use super::source::SourceType;
+    use k8s_openapi::api::core::v1::SecretKeySelector;
+    use numaflow_models::models::gssapi::AuthType;
+    use numaflow_models::models::{Gssapi, KafkaSource, Sasl, SaslPlain, SasloAuth, Tls};
+    use std::fs;
+    use std::path::Path;
+
+    const SECRET_BASE_PATH: &str = "/tmp/numaflow";
+
+    fn setup_secret(name: &str, key: &str, value: &str) {
+        let path = format!("{SECRET_BASE_PATH}/{name}");
+        fs::create_dir_all(&path).unwrap();
+        fs::write(format!("{path}/{key}"), value).unwrap();
+    }
+
+    fn cleanup_secret(name: &str) {
+        let path = format!("{SECRET_BASE_PATH}/{name}");
+        if Path::new(&path).exists() {
+            fs::remove_dir_all(&path).unwrap();
+        }
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_plain_auth() {
+        let secret_name = "test_try_from_kafka_source_with_plain_auth_plain-auth-secret";
+        let user_key = "username";
+        let pass_key = "password";
+        setup_secret(secret_name, user_key, "test-user");
+        setup_secret(secret_name, pass_key, "test-pass");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "PLAIN".to_string(),
+                plain: Some(Box::new(SaslPlain {
+                    handshake: true,
+                    user_secret: SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: user_key.to_string(),
+                        ..Default::default()
+                    },
+                    password_secret: Some(SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: pass_key.to_string(),
+                        ..Default::default()
+                    }),
+                })),
+                scramsha256: None,
+                scramsha512: None,
+                oauth: None,
+                gssapi: None,
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let auth = config.auth.unwrap();
+            match auth {
+                numaflow_kafka::KafkaSaslAuth::Plain { username, password } => {
+                    assert_eq!(username, "test-user");
+                    assert_eq!(password, "test-pass");
+                }
+                _ => panic!("Unexpected KafkaAuth variant"),
+            }
+            assert_eq!(config.brokers, vec!["localhost:9092"]);
+            assert_eq!(config.topics, vec!["test-topic"]);
+            assert_eq!(config.consumer_group, "test-group");
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(secret_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_scram_auth() {
+        let secret_name = "test_try_from_kafka_source_with_scram_auth_scram-auth-secret";
+        let user_key = "username";
+        let pass_key = "password";
+        setup_secret(secret_name, user_key, "test-user");
+        setup_secret(secret_name, pass_key, "test-pass");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "SCRAM-SHA-256".to_string(),
+                plain: None,
+                scramsha256: Some(Box::new(SaslPlain {
+                    handshake: true,
+                    user_secret: SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: user_key.to_string(),
+                        ..Default::default()
+                    },
+                    password_secret: Some(SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: pass_key.to_string(),
+                        ..Default::default()
+                    }),
+                })),
+                scramsha512: None,
+                oauth: None,
+                gssapi: None,
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let auth = config.auth.unwrap();
+            match auth {
+                numaflow_kafka::KafkaSaslAuth::ScramSha256 { username, password } => {
+                    assert_eq!(username, "test-user");
+                    assert_eq!(password, "test-pass");
+                }
+                _ => panic!("Unexpected KafkaAuth variant"),
+            }
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(secret_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_oauth() {
+        let secret_name = "test_try_from_kafka_source_with_oauth_oauth-auth-secret";
+        let client_id_key = "client_id";
+        let client_secret_key = "client_secret";
+        setup_secret(secret_name, client_id_key, "test-client");
+        setup_secret(secret_name, client_secret_key, "test-secret");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "OAUTH".to_string(),
+                plain: None,
+                scramsha256: None,
+                scramsha512: None,
+                oauth: Some(Box::new(SasloAuth {
+                    client_id: SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: client_id_key.to_string(),
+                        ..Default::default()
+                    },
+                    client_secret: SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: client_secret_key.to_string(),
+                        ..Default::default()
+                    },
+                    token_endpoint: "https://oauth.example.com/token".to_string(),
+                })),
+                gssapi: None,
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let auth = config.auth.unwrap();
+            match auth {
+                numaflow_kafka::KafkaSaslAuth::Oauth {
+                    client_id,
+                    client_secret,
+                    token_endpoint,
+                } => {
+                    assert_eq!(client_id, "test-client");
+                    assert_eq!(client_secret, "test-secret");
+                    assert_eq!(token_endpoint, "https://oauth.example.com/token");
+                }
+                _ => panic!("Unexpected KafkaAuth variant"),
+            }
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(secret_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_tls() {
+        let test_name = "test_try_from_kafka_source_with_tls";
+        let ca_cert_name = format!("{test_name}-tls-ca-cert");
+        let cert_name = format!("{test_name}-tls-cert");
+        let key_name = format!("{test_name}-tls-key");
+        setup_secret(&ca_cert_name, "ca", "test-ca-cert");
+        setup_secret(&cert_name, "cert", "test-cert");
+        setup_secret(&key_name, "key", "test-key");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: None,
+            tls: Some(Box::new(Tls {
+                ca_cert_secret: Some(SecretKeySelector {
+                    name: ca_cert_name.clone(),
+                    key: "ca".to_string(),
+                    ..Default::default()
+                }),
+                cert_secret: Some(SecretKeySelector {
+                    name: cert_name.clone(),
+                    key: "cert".to_string(),
+                    ..Default::default()
+                }),
+                key_secret: Some(SecretKeySelector {
+                    name: key_name.clone(),
+                    key: "key".to_string(),
+                    ..Default::default()
+                }),
+                insecure_skip_verify: Some(false),
+            })),
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let tls_config = config.tls.unwrap();
+            assert_eq!(tls_config.ca_cert.unwrap(), "test-ca-cert");
+            let client_auth = tls_config.client_auth.as_ref().unwrap();
+            assert_eq!(client_auth.client_cert, "test-cert");
+            assert_eq!(client_auth.client_cert_private_key, "test-key");
+            assert!(!tls_config.insecure_skip_verify);
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(&ca_cert_name);
+        cleanup_secret(&cert_name);
+        cleanup_secret(&key_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_insecure_tls() {
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: None,
+            tls: Some(Box::new(Tls {
+                ca_cert_secret: None,
+                cert_secret: None,
+                key_secret: None,
+                insecure_skip_verify: Some(true),
+            })),
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let tls_config = config.tls.unwrap();
+            assert!(tls_config.insecure_skip_verify);
+            assert!(tls_config.ca_cert.is_none());
+            assert!(tls_config.client_auth.is_none());
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_invalid_auth() {
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "GSSAPI".to_string(),
+                plain: None,
+                scramsha256: None,
+                scramsha512: None,
+                oauth: None,
+                gssapi: None,
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let result = SourceType::try_from(Box::new(kafka_source));
+        assert!(result.is_err());
+        let err = result.unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Config Error - GSSAPI mechanism requires gssapi configuration"
+        );
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_missing_password() {
+        let secret_name = "test_try_from_kafka_source_with_missing_password_plain-auth-secret";
+        let user_key = "username";
+        setup_secret(secret_name, user_key, "test-user");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "PLAIN".to_string(),
+                plain: Some(Box::new(SaslPlain {
+                    handshake: true,
+                    user_secret: SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: user_key.to_string(),
+                        ..Default::default()
+                    },
+                    password_secret: None,
+                })),
+                scramsha256: None,
+                scramsha512: None,
+                oauth: None,
+                gssapi: None,
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let result = SourceType::try_from(Box::new(kafka_source));
+        assert!(result.is_err());
+        let err = result.unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Config Error - PLAIN mechanism requires password"
+        );
+
+        cleanup_secret(secret_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_missing_client_cert_key() {
+        let cert_name = "test_try_from_kafka_source_with_missing_client_cert_key_tls-cert";
+        setup_secret(cert_name, "cert", "test-cert");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: None,
+            tls: Some(Box::new(Tls {
+                ca_cert_secret: None,
+                cert_secret: Some(SecretKeySelector {
+                    name: cert_name.to_string(),
+                    key: "cert".to_string(),
+                    ..Default::default()
+                }),
+                key_secret: None,
+                insecure_skip_verify: Some(false),
+            })),
+            config: None,
+            kafka_version: None,
+        };
+
+        let result = SourceType::try_from(Box::new(kafka_source));
+        assert!(result.is_err());
+        let err = result.unwrap_err();
+        assert_eq!(
+            err.to_string(),
+            "Config Error - Client cert is specified for TLS authentication, but private key is not specified"
+        );
+
+        cleanup_secret(cert_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_scram_sha512_auth() {
+        let secret_name = "test_try_from_kafka_source_with_scram_sha512_auth_scram-auth-secret";
+        let user_key = "username";
+        let pass_key = "password";
+        setup_secret(secret_name, user_key, "test-user");
+        setup_secret(secret_name, pass_key, "test-pass");
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "SCRAM-SHA-512".to_string(),
+                plain: None,
+                scramsha256: None,
+                scramsha512: Some(Box::new(SaslPlain {
+                    handshake: true,
+                    user_secret: SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: user_key.to_string(),
+                        ..Default::default()
+                    },
+                    password_secret: Some(SecretKeySelector {
+                        name: secret_name.to_string(),
+                        key: pass_key.to_string(),
+                        ..Default::default()
+                    }),
+                })),
+                oauth: None,
+                gssapi: None,
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let auth = config.auth.unwrap();
+            match auth {
+                numaflow_kafka::KafkaSaslAuth::ScramSha512 { username, password } => {
+                    assert_eq!(username, "test-user");
+                    assert_eq!(password, "test-pass");
+                }
+                _ => panic!("Unexpected KafkaAuth variant"),
+            }
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(secret_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_gssapi_auth_password() {
+        let secret_name = "test_try_from_kafka_source_with_gssapi_auth_password_gssapi-secret";
+        let user_key = "username";
+        let pass_key = "password";
+        setup_secret(secret_name, user_key, "test-user");
+        setup_secret(secret_name, pass_key, "test-pass");
+
+        let gssapi = Gssapi {
+            auth_type: AuthType::UserAuth,
+            kerberos_config_secret: None,
+            keytab_secret: None,
+            password_secret: Some(SecretKeySelector {
+                name: secret_name.to_string(),
+                key: pass_key.to_string(),
+                ..Default::default()
+            }),
+            realm: "EXAMPLE.COM".to_string(),
+            service_name: "kafka".to_string(),
+            username_secret: SecretKeySelector {
+                name: secret_name.to_string(),
+                key: user_key.to_string(),
+                ..Default::default()
+            },
+        };
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "GSSAPI".to_string(),
+                plain: None,
+                scramsha256: None,
+                scramsha512: None,
+                oauth: None,
+                gssapi: Some(Box::new(gssapi)),
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let auth = config.auth.unwrap();
+            match auth {
+                numaflow_kafka::KafkaSaslAuth::Gssapi {
+                    service_name,
+                    realm,
+                    username,
+                    password,
+                    keytab,
+                    kerberos_config,
+                    auth_type,
+                } => {
+                    assert_eq!(service_name, "kafka");
+                    assert_eq!(realm, "EXAMPLE.COM");
+                    assert_eq!(username, "test-user");
+                    assert_eq!(password, Some("test-pass".to_string()));
+                    assert!(keytab.is_none());
+                    assert!(kerberos_config.is_none());
+                    assert_eq!(auth_type, "UserAuth");
+                }
+                _ => panic!("Unexpected KafkaAuth variant"),
+            }
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(secret_name);
+    }
+
+    #[test]
+    fn test_try_from_kafka_source_with_gssapi_auth_keytab() {
+        let secret_name = "test_try_from_kafka_source_with_gssapi_auth_keytab_gssapi-secret";
+        let user_key = "username";
+        let keytab_key = "keytab";
+        setup_secret(secret_name, user_key, "test-user");
+        setup_secret(secret_name, keytab_key, "test-keytab");
+
+        let gssapi = Gssapi {
+            auth_type: AuthType::KeytabAuth,
+            kerberos_config_secret: None,
+            keytab_secret: Some(SecretKeySelector {
+                name: secret_name.to_string(),
+                key: keytab_key.to_string(),
+                ..Default::default()
+            }),
+            password_secret: None,
+            realm: "EXAMPLE.COM".to_string(),
+            service_name: "kafka".to_string(),
+            username_secret: SecretKeySelector {
+                name: secret_name.to_string(),
+                key: user_key.to_string(),
+                ..Default::default()
+            },
+        };
+
+        let kafka_source = KafkaSource {
+            brokers: Some(vec!["localhost:9092".to_string()]),
+            topic: "test-topic".to_string(),
+            consumer_group: Some("test-group".to_string()),
+            sasl: Some(Box::new(Sasl {
+                mechanism: "GSSAPI".to_string(),
+                plain: None,
+                scramsha256: None,
+                scramsha512: None,
+                oauth: None,
+                gssapi: Some(Box::new(gssapi)),
+            })),
+            tls: None,
+            config: None,
+            kafka_version: None,
+        };
+
+        let source_type = SourceType::try_from(Box::new(kafka_source)).unwrap();
+        if let SourceType::Kafka(config) = source_type {
+            let auth = config.auth.unwrap();
+            match auth {
+                numaflow_kafka::KafkaSaslAuth::Gssapi {
+                    service_name,
+                    realm,
+                    username,
+                    password,
+                    keytab,
+                    kerberos_config,
+                    auth_type,
+                } => {
+                    assert_eq!(service_name, "kafka");
+                    assert_eq!(realm, "EXAMPLE.COM");
+                    assert_eq!(username, "test-user");
+                    assert!(password.is_none());
+                    assert_eq!(keytab, Some("test-keytab".to_string()));
+                    assert!(kerberos_config.is_none());
+                    assert_eq!(auth_type, "KeytabAuth");
+                }
+                _ => panic!("Unexpected KafkaAuth variant"),
+            }
+        } else {
+            panic!("Expected SourceType::Kafka");
+        }
+
+        cleanup_secret(secret_name);
+    }
+}
+
+#[cfg(test)]
+mod reducer_tests {
+    use std::time::Duration;
+
+    use numaflow_models::models::{
+        AccumulatorWindow, Container, FixedWindow, GroupBy, SessionWindow, SlidingWindow, Udf,
+        Window,
+    };
+
+    use super::reduce::{ReducerType, UserDefinedConfig, WindowConfig, WindowType};
+
+    #[test]
+    fn test_default_user_defined_config() {
+        let default_config = UserDefinedConfig::default();
+        assert_eq!(default_config.grpc_max_message_size, 64 * 1024 * 1024);
+        assert_eq!(default_config.socket_path, "/var/run/numaflow/reduce.sock");
+        assert_eq!(
+            default_config.server_info_path,
+            "/var/run/numaflow/reducer-server-info"
+        );
+    }
+
+    #[test]
+    fn test_reducer_type_from_udf_and_group_by() {
+        let udf = Box::new(Udf {
+            builtin: None,
+            container: Some(Box::new(Container {
+                args: None,
+                command: None,
+                env: None,
+                env_from: None,
+                image: Some("reducer-image".to_string()),
+                image_pull_policy: None,
+                liveness_probe: None,
+                ports: None,
+                readiness_probe: None,
+                resources: None,
+                security_context: None,
+                volume_mounts: None,
+            })),
+            group_by: None,
+        });
+
+        let window = Window {
+            fixed: Some(Box::new(FixedWindow {
+                length: Some(kube::core::Duration::from(Duration::from_secs(60))),
+                streaming: None,
+            })),
+            sliding: None,
+            session: None,
+            accumulator: None,
+        };
+
+        let group_by = Box::new(GroupBy {
+            allowed_lateness: Some(kube::core::Duration::from(Duration::from_secs(10))),
+            keyed: Some(true),
+            storage: None,
+            window: Box::new(window),
+        });
+
+        let reducer_type = ReducerType::try_from((&udf, &group_by)).unwrap();
+        match reducer_type {
+            ReducerType::UserDefined(config) => {
+                assert_eq!(config, UserDefinedConfig::default());
+            }
+        }
+    }
+
+    #[test]
+    fn test_window_config_from_group_by_fixed() {
+        let window = Window {
+            fixed: Some(Box::new(FixedWindow {
+                length: Some(kube::core::Duration::from(Duration::from_secs(60))),
+                streaming: None,
+            })),
+            sliding: None,
+            session: None,
+            accumulator: None,
+        };
+
+        let group_by = Box::new(GroupBy {
+            allowed_lateness: Some(kube::core::Duration::from(Duration::from_secs(10))),
+            keyed: Some(true),
+            storage: None,
+            window: Box::new(window),
+        });
+
+        let window_config = WindowConfig::try_from(&group_by).unwrap();
+        assert_eq!(window_config.allowed_lateness, Duration::from_secs(10));
+        assert!(window_config.is_keyed);
+
+        match window_config.window_type {
+            WindowType::Fixed(config) => {
+                assert_eq!(config.length, Duration::from_secs(60));
+            }
+            _ => panic!("Expected fixed window type"),
+        }
+    }
+
+    #[test]
+    fn test_window_config_from_group_by_sliding() {
+        let window = Window {
+            fixed: None,
+            sliding: Some(Box::new(SlidingWindow {
+                length: Some(kube::core::Duration::from(Duration::from_secs(60))),
+                slide: Some(kube::core::Duration::from(Duration::from_secs(30))),
+                streaming: None,
+            })),
+            session: None,
+            accumulator: None,
+        };
+
+        let group_by = Box::new(GroupBy {
+            allowed_lateness: None,
+            keyed: None,
+            storage: None,
+            window: Box::new(window),
+        });
+
+        let window_config = WindowConfig::try_from(&group_by).unwrap();
+        assert_eq!(window_config.allowed_lateness, Duration::from_secs(0));
+        assert!(!window_config.is_keyed);
+
+        match window_config.window_type {
+            WindowType::Sliding(config) => {
+                assert_eq!(config.length, Duration::from_secs(60));
+                assert_eq!(config.slide, Duration::from_secs(30));
+            }
+            _ => panic!("Expected sliding window type"),
+        }
+    }
+
+    #[test]
+    fn test_window_config_from_group_by_session() {
+        let window = Window {
+            fixed: None,
+            sliding: None,
+            session: Some(Box::new(SessionWindow {
+                timeout: Some(kube::core::Duration::from(Duration::from_secs(300))),
+            })),
+            accumulator: None,
+        };
+
+        let group_by = Box::new(GroupBy {
+            allowed_lateness: None,
+            keyed: Some(true),
+            storage: None,
+            window: Box::new(window),
+        });
+
+        let window_config = WindowConfig::try_from(&group_by).unwrap();
+        assert_eq!(window_config.allowed_lateness, Duration::from_secs(0));
+        assert!(window_config.is_keyed);
+
+        match window_config.window_type {
+            WindowType::Session(config) => {
+                assert_eq!(config.timeout, Duration::from_secs(300));
+            }
+            _ => panic!("Expected session window type"),
+        }
+    }
+
+    #[test]
+    fn test_window_config_from_group_by_accumulator() {
+        let window = Window {
+            fixed: None,
+            sliding: None,
+            session: None,
+            accumulator: Some(Box::new(AccumulatorWindow {
+                timeout: Some(kube::core::Duration::from(Duration::from_secs(600))),
+            })),
+        };
+
+        let group_by = Box::new(GroupBy {
+            allowed_lateness: None,
+            keyed: Some(true),
+            storage: None,
+            window: Box::new(window),
+        });
+
+        let window_config = WindowConfig::try_from(&group_by).unwrap();
+        assert_eq!(window_config.allowed_lateness, Duration::from_secs(0));
+        assert!(window_config.is_keyed);
+
+        match window_config.window_type {
+            WindowType::Accumulator(config) => {
+                assert_eq!(config.timeout, Duration::from_secs(600));
+            }
+            _ => panic!("Expected accumulator window type"),
+        }
+    }
+
+    #[test]
+    fn test_window_config_from_group_by_no_window_type() {
+        let window = Window {
+            fixed: None,
+            sliding: None,
+            session: None,
+            accumulator: None,
+        };
+
+        let group_by = Box::new(GroupBy {
+            allowed_lateness: None,
+            keyed: None,
+            storage: None,
+            window: Box::new(window),
+        });
+
+        let result = WindowConfig::try_from(&group_by);
+        assert!(result.is_err());
+        assert_eq!(
+            result.unwrap_err().to_string(),
+            "Config Error - No window type specified"
+        );
+    }
+}
diff --git a/rust/numaflow-core/src/config/monovertex.rs b/rust/numaflow-core/src/config/monovertex.rs
index 7c9e95c1..edff19e5 100644
--- a/rust/numaflow-core/src/config/monovertex.rs
+++ b/rust/numaflow-core/src/config/monovertex.rs
@@ -250,6 +250,54 @@ mod tests {
         assert!(matches!(config.sink_config.sink_type, SinkType::Log(_)));
     }
 
+    #[test]
+    fn test_load_valid_sqs_sink() {
+        let valid_config = r#"
+        {
+            "metadata": {
+                "name": "test_vertex"
+            },
+            "spec": {
+                "limits": {
+                    "readBatchSize": 1000,
+                    "readTimeout": "2s"
+                },
+                "source": {
+                    "udsource": {
+                        "container": {
+                            "image": "xxxxxxx",
+                            "resources": {}
+                        }
+                    }
+                },
+                "sink": {
+                    "sqs": {
+                        "queueName": "https://sqs.us-east-1.amazonaws.com/123456789012/MyQueue",
+                        "awsRegion": "us-east-1",
+                        "queueOwnerAWSAccountID": "123456789012"
+                    }
+                }
+            }
+        }
+        "#;
+
+        let encoded_valid_config = BASE64_STANDARD.encode(valid_config);
+
+        let mut env_vars = HashMap::new();
+        env_vars.insert(ENV_MONO_VERTEX_OBJ.to_string(), encoded_valid_config);
+
+        let config = MonovertexConfig::load(env_vars).unwrap();
+
+        assert_eq!(config.name, "test_vertex");
+        assert_eq!(config.batch_size, 1000);
+        assert_eq!(config.read_timeout.as_millis(), 2000);
+        assert!(matches!(
+            config.source_config.source_type,
+            SourceType::UserDefined(_)
+        ));
+        assert!(matches!(config.sink_config.sink_type, SinkType::Sqs(_)));
+    }
+
     #[test]
     fn test_load_missing_source() {
         let invalid_config = r#"
diff --git a/rust/numaflow-core/src/config/pipeline.rs b/rust/numaflow-core/src/config/pipeline.rs
index ed99df1e..98fd4517 100644
--- a/rust/numaflow-core/src/config/pipeline.rs
+++ b/rust/numaflow-core/src/config/pipeline.rs
@@ -17,6 +17,9 @@ use crate::Result;
 use crate::config::ENV_NUMAFLOW_SERVING_CALLBACK_STORE;
 use crate::config::ENV_NUMAFLOW_SERVING_SOURCE_SETTINGS;
 use crate::config::components::metrics::MetricsConfig;
+use crate::config::components::reduce::{
+    ReducerConfig, ReducerType, StorageConfig, UserDefinedConfig,
+};
 use crate::config::components::sink::SinkConfig;
 use crate::config::components::sink::SinkType;
 use crate::config::components::source::SourceConfig;
@@ -44,6 +47,10 @@ pub(crate) const DEFAULT_STREAM_MAP_SOCKET: &str = "/var/run/numaflow/mapstream.
 const DEFAULT_MAP_SERVER_INFO_FILE: &str = "/var/run/numaflow/mapper-server-info";
 const DEFAULT_SERVING_STORE_SOCKET: &str = "/var/run/numaflow/serving.sock";
 const DEFAULT_SERVING_STORE_SERVER_INFO_FILE: &str = "/var/run/numaflow/serving-server-info";
+pub(crate) const VERTEX_TYPE_SOURCE: &str = "Source";
+pub(crate) const VERTEX_TYPE_SINK: &str = "Sink";
+pub(crate) const VERTEX_TYPE_MAP_UDF: &str = "MapUDF";
+pub(crate) const VERTEX_TYPE_REDUCE_UDF: &str = "ReduceUDF";
 
 pub(crate) mod isb;
 pub(crate) mod watermark;
@@ -191,6 +198,13 @@ pub(crate) enum VertexType {
     Source(SourceVtxConfig),
     Sink(SinkVtxConfig),
     Map(MapVtxConfig),
+    Reduce(ReduceVtxConfig),
+}
+
+#[derive(Debug, Clone, PartialEq)]
+pub(crate) struct ReduceVtxConfig {
+    pub(crate) reducer_config: ReducerConfig,
+    pub(crate) wal_storage_config: Option<StorageConfig>,
 }
 
 #[derive(Debug, Deserialize, Clone, PartialEq)]
@@ -225,9 +239,10 @@ impl Default for UserDefinedStoreConfig {
 impl std::fmt::Display for VertexType {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
         match self {
-            VertexType::Source(_) => write!(f, "Source"),
-            VertexType::Sink(_) => write!(f, "Sink"),
-            VertexType::Map(_) => write!(f, "Map"),
+            VertexType::Source(_) => write!(f, "{}", VERTEX_TYPE_SOURCE),
+            VertexType::Sink(_) => write!(f, "{}", VERTEX_TYPE_SINK),
+            VertexType::Map(_) => write!(f, "{}", VERTEX_TYPE_MAP_UDF),
+            VertexType::Reduce(_) => write!(f, "{}", VERTEX_TYPE_REDUCE_UDF),
         }
     }
 }
@@ -271,8 +286,6 @@ impl PipelineConfig {
             })
             .collect();
 
-        info!("Env vars found - {:#?}", env_vars);
-
         let get_var = |var: &str| -> Result<String> {
             Ok(env_vars
                 .get(var)
@@ -386,20 +399,42 @@ impl PipelineConfig {
             VertexType::Sink(SinkVtxConfig {
                 sink_config: SinkConfig {
                     sink_type: SinkType::primary_sinktype(&sink)?,
-                    retry_config: None,
+                    retry_config: sink.retry_strategy.clone().map(|retry| retry.into()),
                 },
                 fb_sink_config,
                 serving_store_config,
             })
-        } else if let Some(map) = vertex_obj.spec.udf {
-            VertexType::Map(MapVtxConfig {
-                concurrency: batch_size as usize,
-                map_type: map.try_into()?,
-                map_mode: MapMode::Unary,
-            })
+        } else if let Some(udf) = vertex_obj.spec.udf {
+            if let Some(group_by) = &udf.group_by {
+                // This is a reduce vertex
+                let reducer_config = ReducerConfig {
+                    reducer_type: ReducerType::UserDefined(UserDefinedConfig::default()),
+                    window_config: group_by.try_into()?,
+                };
+
+                let storage_config = group_by.storage.as_ref().and_then(|storage| {
+                    if storage.no_store.is_some() {
+                        None
+                    } else {
+                        Some(Default::default())
+                    }
+                });
+
+                VertexType::Reduce(ReduceVtxConfig {
+                    reducer_config,
+                    wal_storage_config: storage_config,
+                })
+            } else {
+                // This is a map vertex
+                VertexType::Map(MapVtxConfig {
+                    concurrency: batch_size as usize,
+                    map_type: udf.try_into()?,
+                    map_mode: MapMode::Unary,
+                })
+            }
         } else {
             return Err(Error::Config(
-                "Only source and sink are supported ATM".to_string(),
+                "Only source, sink, map, and reduce are supported".to_string(),
             ));
         };
 
@@ -610,7 +645,7 @@ impl PipelineConfig {
                     .collect(),
                 idle_config,
             })),
-            VertexType::Sink(_) | VertexType::Map(_) => {
+            VertexType::Sink(_) | VertexType::Map(_) | VertexType::Reduce(_) => {
                 Some(WatermarkConfig::Edge(EdgeWatermarkConfig {
                     from_vertex_config: from_vertex_config
                         .iter()
@@ -666,7 +701,7 @@ mod tests {
     use numaflow_pulsar::source::PulsarSourceConfig;
 
     use super::*;
-    use crate::config::components::sink::{BlackholeConfig, LogConfig, SinkType};
+    use crate::config::components::sink::{BlackholeConfig, LogConfig, RetryConfig, SinkType};
     use crate::config::components::source::{GeneratorConfig, SourceType};
     use crate::config::pipeline::map::{MapType, UserDefinedConfig};
 
@@ -753,7 +788,7 @@ mod tests {
             vertex_type_config: VertexType::Sink(SinkVtxConfig {
                 sink_config: SinkConfig {
                     sink_type: SinkType::Blackhole(BlackholeConfig {}),
-                    retry_config: None,
+                    retry_config: Some(RetryConfig::default()),
                 },
                 fb_sink_config: None,
                 serving_store_config: Some(ServingStoreType::Nats(NatsStoreConfig {
diff --git a/rust/numaflow-core/src/error.rs b/rust/numaflow-core/src/error.rs
index 6f3e4b50..a06d50cc 100644
--- a/rust/numaflow-core/src/error.rs
+++ b/rust/numaflow-core/src/error.rs
@@ -62,6 +62,12 @@ pub enum Error {
     #[error("Watermark Error - {0}")]
     Watermark(String),
 
+    #[error("Reduce Error - {0}")]
+    Reduce(String),
+
+    #[error("Cancellation Token Cancelled")]
+    Cancelled(),
+
     #[error("WAL Error - {0}")]
     #[allow(clippy::upper_case_acronyms)]
     WAL(String),
diff --git a/rust/numaflow-core/src/mapper/map.rs b/rust/numaflow-core/src/mapper/map.rs
index 42ebe42a..510c57ec 100644
--- a/rust/numaflow-core/src/mapper/map.rs
+++ b/rust/numaflow-core/src/mapper/map.rs
@@ -704,8 +704,7 @@ mod tests {
                 offset: "0".to_string().into(),
                 index: 0,
             },
-            headers: Default::default(),
-            metadata: None,
+            ..Default::default()
         };
 
         let (output_tx, mut output_rx) = mpsc::channel(10);
@@ -800,8 +799,7 @@ mod tests {
                     offset: i.to_string().into(),
                     index: i,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             };
             input_tx.send(message).await.unwrap();
         }
@@ -896,8 +894,7 @@ mod tests {
                     offset: i.to_string().into(),
                     index: i,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             };
             input_tx.send(message).await.unwrap();
             sleep(Duration::from_millis(10)).await;
@@ -991,8 +988,7 @@ mod tests {
                     offset: "0".to_string().into(),
                     index: 0,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             },
             Message {
                 typ: Default::default(),
@@ -1007,8 +1003,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 1,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             },
         ];
 
@@ -1105,8 +1100,7 @@ mod tests {
                     offset: "0".to_string().into(),
                     index: 0,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             },
             Message {
                 typ: Default::default(),
@@ -1121,8 +1115,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 1,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             },
         ];
 
@@ -1219,8 +1212,7 @@ mod tests {
                 offset: "0".to_string().into(),
                 index: 0,
             },
-            headers: Default::default(),
-            metadata: None,
+            ..Default::default()
         };
 
         let (input_tx, input_rx) = mpsc::channel(10);
@@ -1325,8 +1317,7 @@ mod tests {
                     offset: i.to_string().into(),
                     index: i,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             };
             input_tx.send(message).await.unwrap();
             sleep(Duration::from_millis(10)).await;
diff --git a/rust/numaflow-core/src/mapper/map/user_defined.rs b/rust/numaflow-core/src/mapper/map/user_defined.rs
index 5474e565..aa4d0e63 100644
--- a/rust/numaflow-core/src/mapper/map/user_defined.rs
+++ b/rust/numaflow-core/src/mapper/map/user_defined.rs
@@ -1,8 +1,8 @@
-use std::collections::HashMap;
-use std::sync::Arc;
-
 use chrono::{DateTime, Utc};
 use numaflow_pb::clients::map::{self, MapRequest, MapResponse, map_client::MapClient};
+use std::collections::HashMap;
+use std::sync::Arc;
+use std::time::Instant;
 use tokio::sync::Mutex;
 use tokio::sync::{mpsc, oneshot};
 use tokio_stream::wrappers::ReceiverStream;
@@ -11,8 +11,10 @@ use tonic::{Request, Streaming};
 use tracing::error;
 
 use crate::config::get_vertex_name;
+use crate::config::pipeline::VERTEX_TYPE_MAP_UDF;
 use crate::error::{Error, Result};
 use crate::message::{Message, MessageID, Offset};
+use crate::metrics::{pipeline_metric_labels, pipeline_metrics};
 use crate::shared::grpc::prost_timestamp_from_utc;
 
 type ResponseSenderMap =
@@ -24,7 +26,9 @@ type StreamResponseSenderMap =
 struct ParentMessageInfo {
     offset: Offset,
     event_time: DateTime<Utc>,
+    is_late: bool,
     headers: HashMap<String, String>,
+    start_time: Instant,
 }
 
 impl From<Message> for MapRequest {
@@ -99,6 +103,11 @@ impl UserDefinedUnaryMap {
                 let mut senders = sender_map.lock().await;
                 for (_, (_, sender)) in senders.drain() {
                     let _ = sender.send(Err(Error::Grpc(e.clone())));
+                    pipeline_metrics()
+                        .forwarder
+                        .udf_error_total
+                        .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                        .inc();
                 }
                 None
             }
@@ -118,8 +127,16 @@ impl UserDefinedUnaryMap {
             offset: message.offset.clone(),
             event_time: message.event_time,
             headers: message.headers.clone(),
+            is_late: message.is_late,
+            start_time: Instant::now(),
         };
 
+        pipeline_metrics()
+            .forwarder
+            .udf_read_total
+            .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+            .inc();
+
         self.senders
             .lock()
             .await
@@ -185,6 +202,11 @@ impl UserDefinedBatchMap {
                     sender
                         .send(Err(Error::Grpc(e.clone())))
                         .expect("failed to send error response");
+                    pipeline_metrics()
+                        .forwarder
+                        .udf_error_total
+                        .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                        .inc();
                 }
                 None
             }
@@ -193,9 +215,13 @@ impl UserDefinedBatchMap {
                 if !sender_map.lock().await.is_empty() {
                     error!("received EOT but not all responses have been received");
                 }
+                pipeline_metrics()
+                    .forwarder
+                    .udf_processing_time
+                    .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                    .observe(Instant::now().elapsed().as_micros() as f64);
                 continue;
             }
-
             process_response(&sender_map, resp).await
         }
     }
@@ -212,8 +238,16 @@ impl UserDefinedBatchMap {
                 offset: message.offset.clone(),
                 event_time: message.event_time,
                 headers: message.headers.clone(),
+                is_late: message.is_late,
+                start_time: Instant::now(),
             };
 
+            pipeline_metrics()
+                .forwarder
+                .udf_read_total
+                .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                .inc();
+
             self.senders
                 .lock()
                 .await
@@ -246,6 +280,19 @@ async fn process_response(sender_map: &ResponseSenderMap, resp: MapResponse) {
         for (i, result) in resp.results.into_iter().enumerate() {
             response_messages.push(UserDefinedMessage(result, &msg_info, i as i32).into());
         }
+
+        pipeline_metrics()
+            .forwarder
+            .udf_write_total
+            .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+            .inc_by(response_messages.len() as u64);
+
+        pipeline_metrics()
+            .forwarder
+            .udf_processing_time
+            .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+            .observe(msg_info.start_time.elapsed().as_micros() as f64);
+
         sender
             .send(Ok(response_messages))
             .expect("failed to send response");
@@ -345,6 +392,11 @@ impl UserDefinedStreamMap {
                 let mut senders = sender_map.lock().await;
                 for (_, (_, sender)) in senders.drain() {
                     let _ = sender.send(Err(Error::Grpc(e.clone()))).await;
+                    pipeline_metrics()
+                        .forwarder
+                        .udf_error_total
+                        .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                        .inc();
                 }
                 None
             }
@@ -355,9 +407,20 @@ impl UserDefinedStreamMap {
                 .remove(&resp.id)
                 .expect("map entry should always be present");
 
+            pipeline_metrics()
+                .forwarder
+                .udf_write_total
+                .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                .inc();
+
             // once we get eot, we can drop the sender to let the callee
             // know that we are done sending responses
             if let Some(map::TransmissionStatus { eot: true }) = resp.status {
+                pipeline_metrics()
+                    .forwarder
+                    .udf_processing_time
+                    .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+                    .observe(message_info.start_time.elapsed().as_micros() as f64);
                 continue;
             }
 
@@ -390,8 +453,16 @@ impl UserDefinedStreamMap {
             offset: message.offset.clone(),
             event_time: message.event_time,
             headers: message.headers.clone(),
+            start_time: Instant::now(),
+            is_late: message.is_late,
         };
 
+        pipeline_metrics()
+            .forwarder
+            .udf_read_total
+            .get_or_create(pipeline_metric_labels(VERTEX_TYPE_MAP_UDF))
+            .inc();
+
         self.senders
             .lock()
             .await
@@ -423,6 +494,7 @@ impl From<UserDefinedMessage<'_>> for Message {
             headers: value.1.headers.clone(),
             watermark: None,
             metadata: None,
+            is_late: value.1.is_late,
         }
     }
 }
@@ -495,8 +567,7 @@ mod tests {
                 offset: "0".to_string().into(),
                 index: 0,
             },
-            headers: Default::default(),
-            metadata: None,
+            ..Default::default()
         };
 
         let (tx, rx) = tokio::sync::oneshot::channel();
@@ -585,8 +656,7 @@ mod tests {
                     offset: "0".to_string().into(),
                     index: 0,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             },
             crate::message::Message {
                 typ: Default::default(),
@@ -601,8 +671,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 1,
                 },
-                headers: Default::default(),
-                metadata: None,
+                ..Default::default()
             },
         ];
 
@@ -700,8 +769,7 @@ mod tests {
                 offset: "0".to_string().into(),
                 index: 0,
             },
-            headers: Default::default(),
-            metadata: None,
+            ..Default::default()
         };
 
         let (tx, mut rx) = tokio::sync::mpsc::channel(3);
diff --git a/rust/numaflow-core/src/message.rs b/rust/numaflow-core/src/message.rs
index 49c29db3..656206c9 100644
--- a/rust/numaflow-core/src/message.rs
+++ b/rust/numaflow-core/src/message.rs
@@ -39,6 +39,9 @@ pub(crate) struct Message {
     pub(crate) headers: HashMap<String, String>,
     /// Additional metadata that could be passed per message between the vertices.
     pub(crate) metadata: Option<Metadata>,
+    /// is_late is used to indicate if the message is a late data. Late data is data that arrives
+    /// after the watermark has passed. This is set only at source.
+    pub(crate) is_late: bool,
 }
 
 /// Type of the [Message].
@@ -93,6 +96,7 @@ impl Default for Message {
             headers: HashMap::new(),
             metadata: None,
             typ: Default::default(),
+            is_late: false,
         }
     }
 }
@@ -259,7 +263,7 @@ impl TryFrom<Message> for BytesMut {
             header: Some(numaflow_pb::objects::isb::Header {
                 message_info: Some(numaflow_pb::objects::isb::MessageInfo {
                     event_time: Some(prost_timestamp_from_utc(message.event_time)),
-                    is_late: false, // Set this according to your logic
+                    is_late: message.is_late,
                 }),
                 kind: message.typ.into(),
                 id: Some(message.id.into()),
@@ -330,6 +334,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         let result: Result<BytesMut> = message.clone().try_into();
diff --git a/rust/numaflow-core/src/metrics.rs b/rust/numaflow-core/src/metrics.rs
index 170f9086..03d1ef66 100644
--- a/rust/numaflow-core/src/metrics.rs
+++ b/rust/numaflow-core/src/metrics.rs
@@ -1,8 +1,8 @@
 use std::collections::{BTreeMap, HashMap};
-use std::iter;
 use std::net::SocketAddr;
 use std::sync::{Arc, OnceLock};
 use std::time::Duration;
+use std::{env, iter};
 
 use axum::body::Body;
 use axum::extract::State;
@@ -23,9 +23,11 @@ use tokio::time;
 use tracing::{debug, error, info};
 
 use crate::Error;
+use crate::config::pipeline::VERTEX_TYPE_SOURCE;
 use crate::config::{get_pipeline_name, get_vertex_name, get_vertex_replica};
 use crate::mapper::map::MapHandle;
 use crate::pipeline::isb::jetstream::reader::JetStreamReader;
+use crate::reduce::reducer::aligned::user_defined::UserDefinedAlignedReduce;
 use crate::sink::SinkWriter;
 use crate::source::Source;
 
@@ -48,6 +50,7 @@ const PIPELINE_REPLICA_LABEL: &str = "replica";
 pub(crate) const PIPELINE_PARTITION_NAME_LABEL: &str = "partition_name";
 const PIPELINE_VERTEX_LABEL: &str = "vertex";
 const PIPELINE_VERTEX_TYPE_LABEL: &str = "vertex_type";
+const PIPELINE_DROP_REASON_LABEL: &str = "reason";
 
 // The top-level metric registry is created with the GLOBAL_PREFIX
 const MVTX_REGISTRY_GLOBAL_PREFIX: &str = "monovtx";
@@ -64,14 +67,37 @@ const TRANSFORMER_REGISTRY_PREFIX: &str = "transformer";
 
 // counters (please note the prefix _total, and read above link)
 const READ_TOTAL: &str = "read";
+const DATA_READ_TOTAL: &str = "data_read";
 const READ_BYTES_TOTAL: &str = "read_bytes";
+const DATA_READ_BYTES_TOTAL: &str = "data_read_bytes";
+const READ_ERROR_TOTAL: &str = "read_error";
+const UDF_READ_TOTAL: &str = "udf_read";
+
+const WRITE_TOTAL: &str = "write";
+const UDF_WRITE_TOTAL: &str = "udf_write";
+const WRITE_BYTES_TOTAL: &str = "write_bytes";
+const WRITE_ERROR_TOTAL: &str = "write_error";
 const ACK_TOTAL: &str = "ack";
+const UDF_ERROR_TOTAL: &str = "udf_error";
+
 const SINK_WRITE_TOTAL: &str = "write";
 const SINK_WRITE_ERRORS_TOTAL: &str = "write_errors";
+const FALLBACK_SINK_WRITE_ERRORS_TOTAL: &str = "fbsink_write_errors";
+
 const SINK_DROPPED_TOTAL: &str = "dropped";
 const DROPPED_TOTAL: &str = "dropped";
+const PIPELINE_FORWARDER_DROP_TOTAL: &str = "drop";
+const PIPELINE_FORWARDER_DROP_BYTES_TOTAL: &str = "drop_bytes";
+const UDF_DROP_TOTAL: &str = "ud_drop";
+
 const FALLBACK_SINK_WRITE_TOTAL: &str = "write";
+const PIPELINE_FALLBACK_SINK_WRITE_TOTAL: &str = "fbsink_write";
+const PIPELINE_FALLBACK_SINK_WRITE_BYTES_TOTAL: &str = "fbsink_write_bytes";
 const TRANSFORMER_DROPPED_TOTAL: &str = "dropped";
+const TRANSFORMER_ERROR_TOTAL: &str = "transformer_error";
+const TRANSFORMER_READ_TOTAL: &str = "transformer_read";
+const TRANSFORMER_WRITE_TOTAL: &str = "transformer_write";
+const PIPELINE_TRANSFORMER_DROP_TOTAL: &str = "transformer_drop";
 
 // pending as gauge for mvtx (these metric names are hardcoded in the auto-scaler)
 const PENDING: &str = "pending";
@@ -83,14 +109,18 @@ const VERTEX_PENDING_RAW: &str = "pending_messages_raw";
 // processing times as timers
 const E2E_TIME: &str = "processing_time";
 const READ_TIME: &str = "read_time";
-const WRITE_TIME: &str = "write_time";
+const READ_PROCESSING_TIME: &str = "read_processing_time";
+const WRITE_PROCESSING_TIME: &str = "write_processing_time";
+const ACK_PROCESSING_TIME: &str = "ack_processing_time";
+const TRANSFORMER_PROCESSING_TIME: &str = "transformer_processing_time";
+const FORWARD_CHUNK_PROCESSING_TIME: &str = "forward_chunk_processing_time";
+const UDF_PROCESSING_TIME: &str = "udf_processing_time";
+const FALLBACK_SINK_WRITE_PROCESSING_TIME: &str = "fbsink_write_processing_time";
 const TRANSFORM_TIME: &str = "time";
 const ACK_TIME: &str = "ack_time";
 const SINK_TIME: &str = "time";
 const FALLBACK_SINK_TIME: &str = "time";
 
-const PIPELINE_FORWARDER_READ_TOTAL: &str = "data_read";
-
 /// A deep healthcheck for components. Each component should implement IsReady for both builtins and
 /// user-defined containers.
 #[derive(Clone)]
@@ -114,6 +144,7 @@ pub(crate) enum PipelineComponents {
     Source(Source),
     Sink(SinkWriter),
     Map(MapHandle),
+    Reduce(UserDefinedAlignedReduce),
 }
 
 /// The global register of all metrics.
@@ -200,9 +231,13 @@ pub(crate) struct MonoVtxMetrics {
 }
 
 /// PipelineMetrics is a struct which is used for storing the metrics related to the Pipeline
-// TODO: Add the metrics for the pipeline
 pub(crate) struct PipelineMetrics {
+    // generic forwarder metrics
     pub(crate) forwarder: PipelineForwarderMetrics,
+    // source forwarder specific metrics
+    pub(crate) source_forwarder: SourceForwarderMetrics,
+    // sink forwarder specific metrics
+    pub(crate) sink_forwarder: SinkForwarderMetrics,
     pub(crate) isb: PipelineISBMetrics,
     pub(crate) pending: Family<Vec<(String, String)>, Gauge>,
     // TODO(lookback) - using new implementation only for monovertex right now,
@@ -232,16 +267,68 @@ pub(crate) struct TransformerMetrics {
     pub(crate) dropped_total: Family<Vec<(String, String)>, Counter>,
 }
 
+/// Generic forwarder metrics
 pub(crate) struct PipelineForwarderMetrics {
+    // read counters
     pub(crate) read_total: Family<Vec<(String, String)>, Counter>,
-    pub(crate) read_time: Family<Vec<(String, String)>, Histogram>,
-    pub(crate) ack_total: Family<Vec<(String, String)>, Counter>,
-    pub(crate) ack_time: Family<Vec<(String, String)>, Histogram>,
-    pub(crate) write_total: Family<Vec<(String, String)>, Counter>,
-    pub(crate) write_time: Family<Vec<(String, String)>, Histogram>,
+    pub(crate) data_read_total: Family<Vec<(String, String)>, Counter>,
     pub(crate) read_bytes_total: Family<Vec<(String, String)>, Counter>,
-    pub(crate) processed_time: Family<Vec<(String, String)>, Histogram>,
-    pub(crate) dropped_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) data_read_bytes_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) read_error_total: Family<Vec<(String, String)>, Counter>,
+
+    // write counters
+    pub(crate) write_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) write_bytes_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) write_error_total: Family<Vec<(String, String)>, Counter>,
+
+    // drop counters
+    pub(crate) drop_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) drop_bytes_total: Family<Vec<(String, String)>, Counter>,
+
+    // ack counters
+    pub(crate) ack_total: Family<Vec<(String, String)>, Counter>,
+
+    // udf specific counters
+    pub(crate) udf_read_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) udf_write_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) udf_drop_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) udf_error_total: Family<Vec<(String, String)>, Counter>,
+
+    // read histograms
+    pub(crate) read_processing_time: Family<Vec<(String, String)>, Histogram>,
+
+    // write histograms
+    pub(crate) write_processing_time: Family<Vec<(String, String)>, Histogram>,
+
+    // ack histograms
+    pub(crate) ack_processing_time: Family<Vec<(String, String)>, Histogram>,
+
+    // forwarder histograms
+    pub(crate) forward_chunk_processing_time: Family<Vec<(String, String)>, Histogram>,
+
+    // udf histograms
+    pub(crate) udf_processing_time: Family<Vec<(String, String)>, Histogram>,
+}
+
+pub(crate) struct SourceForwarderMetrics {
+    // source transformer counters
+    pub(crate) transformer_read_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) transformer_write_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) transformer_error_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) transformer_drop_total: Family<Vec<(String, String)>, Counter>,
+
+    // source transformer histogram
+    pub(crate) transformer_processing_time: Family<Vec<(String, String)>, Histogram>,
+}
+
+pub(crate) struct SinkForwarderMetrics {
+    // fallback sink counters
+    pub(crate) fbsink_write_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) fbsink_write_bytes_total: Family<Vec<(String, String)>, Counter>,
+    pub(crate) fbsink_write_error_total: Family<Vec<(String, String)>, Counter>,
+
+    // fallback sink histograms
+    pub(crate) fbsink_write_processing_time: Family<Vec<(String, String)>, Histogram>,
 }
 
 pub(crate) struct PipelineISBMetrics {
@@ -430,22 +517,59 @@ impl PipelineMetrics {
         let metrics = Self {
             forwarder: PipelineForwarderMetrics {
                 read_total: Family::<Vec<(String, String)>, Counter>::default(),
-                processed_time: Family::<Vec<(String, String)>, Histogram>::new_with_constructor(
-                    || Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10)),
-                ),
-                read_time: Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
-                    Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10))
-                }),
+                data_read_total: Family::<Vec<(String, String)>, Counter>::default(),
                 read_bytes_total: Family::<Vec<(String, String)>, Counter>::default(),
-                ack_total: Family::<Vec<(String, String)>, Counter>::default(),
-                ack_time: Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
-                    Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10))
-                }),
+                data_read_bytes_total: Family::<Vec<(String, String)>, Counter>::default(),
+                read_error_total: Family::<Vec<(String, String)>, Counter>::default(),
                 write_total: Family::<Vec<(String, String)>, Counter>::default(),
-                write_time: Family::<Vec<(String, String)>, Histogram>::new_with_constructor(
-                    || Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10)),
-                ),
-                dropped_total: Family::<Vec<(String, String)>, Counter>::default(),
+                write_bytes_total: Family::<Vec<(String, String)>, Counter>::default(),
+                write_error_total: Family::<Vec<(String, String)>, Counter>::default(),
+                drop_total: Family::<Vec<(String, String)>, Counter>::default(),
+                drop_bytes_total: Family::<Vec<(String, String)>, Counter>::default(),
+                ack_total: Family::<Vec<(String, String)>, Counter>::default(),
+                udf_read_total: Family::<Vec<(String, String)>, Counter>::default(),
+                udf_drop_total: Family::<Vec<(String, String)>, Counter>::default(),
+                udf_error_total: Family::<Vec<(String, String)>, Counter>::default(),
+                udf_write_total: Family::<Vec<(String, String)>, Counter>::default(),
+                read_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 10.0, 10))
+                    }),
+                write_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 20.0, 10))
+                    }),
+                ack_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 10.0, 10))
+                    }),
+                udf_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10))
+                    }),
+                forward_chunk_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 20.0, 10))
+                    }),
+            },
+            source_forwarder: SourceForwarderMetrics {
+                transformer_read_total: Family::<Vec<(String, String)>, Counter>::default(),
+                transformer_write_total: Family::<Vec<(String, String)>, Counter>::default(),
+                transformer_drop_total: Family::<Vec<(String, String)>, Counter>::default(),
+                transformer_error_total: Family::<Vec<(String, String)>, Counter>::default(),
+                transformer_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 15.0, 10))
+                    }),
+            },
+            sink_forwarder: SinkForwarderMetrics {
+                fbsink_write_total: Family::<Vec<(String, String)>, Counter>::default(),
+                fbsink_write_bytes_total: Family::<Vec<(String, String)>, Counter>::default(),
+                fbsink_write_error_total: Family::<Vec<(String, String)>, Counter>::default(),
+                fbsink_write_processing_time:
+                    Family::<Vec<(String, String)>, Histogram>::new_with_constructor(|| {
+                        Histogram::new(exponential_buckets_range(100.0, 60000000.0 * 20.0, 10))
+                    }),
             },
             isb: PipelineISBMetrics {
                 paf_resolution_time:
@@ -464,49 +588,157 @@ impl PipelineMetrics {
         // Pipeline forwarder sub-registry
         let forwarder_registry = registry.sub_registry_with_prefix("forwarder");
         forwarder_registry.register(
-            PIPELINE_FORWARDER_READ_TOTAL,
-            "Total number of Data Messages Read",
+            READ_TOTAL,
+            "Total number of Messages Read",
             metrics.forwarder.read_total.clone(),
         );
-        forwarder_registry.register(
-            READ_TIME,
-            "Time taken to read data",
-            metrics.forwarder.read_time.clone(),
-        );
         forwarder_registry.register(
             READ_BYTES_TOTAL,
             "Total number of bytes read",
             metrics.forwarder.read_bytes_total.clone(),
         );
         forwarder_registry.register(
-            E2E_TIME,
-            "Time taken to process data",
-            metrics.forwarder.processed_time.clone(),
+            DATA_READ_TOTAL,
+            "Total number of Data Messages Read",
+            metrics.forwarder.data_read_total.clone(),
+        );
+        forwarder_registry.register(
+            DATA_READ_BYTES_TOTAL,
+            "Total number of Data message bytes read",
+            metrics.forwarder.data_read_bytes_total.clone(),
+        );
+        forwarder_registry.register(
+            READ_ERROR_TOTAL,
+            "Total number of Read Errors",
+            metrics.forwarder.read_error_total.clone(),
+        );
+        forwarder_registry.register(
+            READ_PROCESSING_TIME,
+            "Processing times of read operations (100 microseconds to 10 minutes)",
+            metrics.forwarder.read_processing_time.clone(),
+        );
+        forwarder_registry.register(
+            WRITE_TOTAL,
+            "Total number of Messages Written",
+            metrics.forwarder.write_total.clone(),
+        );
+        forwarder_registry.register(
+            WRITE_BYTES_TOTAL,
+            "Total number of Messages Written",
+            metrics.forwarder.write_bytes_total.clone(),
+        );
+        forwarder_registry.register(
+            WRITE_ERROR_TOTAL,
+            "Total number of Write Errors",
+            metrics.forwarder.write_error_total.clone(),
+        );
+        forwarder_registry.register(
+            WRITE_PROCESSING_TIME,
+            "Processing times of write operations (100 microseconds to 20 minutes)",
+            metrics.forwarder.write_processing_time.clone(),
+        );
+        forwarder_registry.register(
+            PIPELINE_FORWARDER_DROP_TOTAL,
+            "Total number of Messages Dropped",
+            metrics.forwarder.drop_total.clone(),
+        );
+        forwarder_registry.register(
+            PIPELINE_FORWARDER_DROP_BYTES_TOTAL,
+            "Total number of Bytes Dropped",
+            metrics.forwarder.drop_bytes_total.clone(),
         );
         forwarder_registry.register(
             ACK_TOTAL,
-            "Total number of Ack Messages",
+            "Total number of Messages Acknowledged",
             metrics.forwarder.ack_total.clone(),
         );
         forwarder_registry.register(
-            ACK_TIME,
-            "Time taken to ack data",
-            metrics.forwarder.ack_time.clone(),
+            ACK_PROCESSING_TIME,
+            "Processing times of acknowledgment operations (100 microseconds to 10 minutes)",
+            metrics.forwarder.ack_processing_time.clone(),
         );
         forwarder_registry.register(
-            SINK_WRITE_TOTAL,
-            "Total number of Data Messages Written",
-            metrics.forwarder.write_total.clone(),
+            UDF_ERROR_TOTAL,
+            "Total number of UDF Errors",
+            metrics.forwarder.udf_error_total.clone(),
         );
         forwarder_registry.register(
-            DROPPED_TOTAL,
-            "Total number of dropped messages",
-            metrics.forwarder.dropped_total.clone(),
+            FORWARD_CHUNK_PROCESSING_TIME,
+            "Processing times of the entire forward a chunk (100 microseconds to 20 minutes)",
+            metrics.forwarder.forward_chunk_processing_time.clone(),
+        );
+        forwarder_registry.register(
+            UDF_PROCESSING_TIME,
+            "Processing times of UDF (100 microseconds to 15 minutes)",
+            metrics.forwarder.udf_processing_time.clone(),
+        );
+        forwarder_registry.register(
+            UDF_READ_TOTAL,
+            "Total number of Messages Read by UDF",
+            metrics.forwarder.udf_read_total.clone(),
         );
         forwarder_registry.register(
-            WRITE_TIME,
-            "Time taken to write data",
-            metrics.forwarder.write_time.clone(),
+            UDF_WRITE_TOTAL,
+            "Total number of Messages Written by UDF",
+            metrics.forwarder.udf_write_total.clone(),
+        );
+        forwarder_registry.register(
+            UDF_DROP_TOTAL,
+            "Total messages dropped by the user",
+            metrics.forwarder.udf_drop_total.clone(),
+        );
+
+        // Pipeline source forwarder sub-registry
+        let source_forwarder_registry = registry.sub_registry_with_prefix("source_forwarder");
+
+        source_forwarder_registry.register(
+            TRANSFORMER_ERROR_TOTAL,
+            "Total number of source transformer Errors",
+            metrics.source_forwarder.transformer_error_total.clone(),
+        );
+        source_forwarder_registry.register(
+            TRANSFORMER_READ_TOTAL,
+            "Total number of Messages Read by source transformer",
+            metrics.source_forwarder.transformer_read_total.clone(),
+        );
+        source_forwarder_registry.register(
+            TRANSFORMER_WRITE_TOTAL,
+            "Total number of Messages Written by source transformer",
+            metrics.source_forwarder.transformer_write_total.clone(),
+        );
+        source_forwarder_registry.register(
+            PIPELINE_TRANSFORMER_DROP_TOTAL,
+            "Total number of Messages Dropped by source transformer",
+            metrics.source_forwarder.transformer_drop_total.clone(),
+        );
+        source_forwarder_registry.register(
+            TRANSFORMER_PROCESSING_TIME,
+            "Processing times of source transformer (100 microseconds to 15 minutes)",
+            metrics.source_forwarder.transformer_processing_time.clone(),
+        );
+
+        // Pipeline sink forwarder sub-registry
+        let sink_forwarder_registry = registry.sub_registry_with_prefix("forwarder");
+
+        sink_forwarder_registry.register(
+            FALLBACK_SINK_WRITE_ERRORS_TOTAL,
+            "Total number of Write Errors while writing to a fallback sink",
+            metrics.sink_forwarder.fbsink_write_error_total.clone(),
+        );
+        sink_forwarder_registry.register(
+            PIPELINE_FALLBACK_SINK_WRITE_TOTAL,
+            "Total number of Messages written to a fallback sink",
+            metrics.sink_forwarder.fbsink_write_total.clone(),
+        );
+        sink_forwarder_registry.register(
+            PIPELINE_FALLBACK_SINK_WRITE_BYTES_TOTAL,
+            "Total number of bytes written to a fallback sink",
+            metrics.sink_forwarder.fbsink_write_bytes_total.clone(),
+        );
+        sink_forwarder_registry.register(
+            FALLBACK_SINK_WRITE_PROCESSING_TIME,
+            "Processing times of write operations to a fallback sink (100 microseconds to 20 minutes)",
+            metrics.sink_forwarder.fbsink_write_processing_time.clone(),
         );
 
         let vertex_registry = registry.sub_registry_with_prefix("vertex");
@@ -527,7 +759,7 @@ impl PipelineMetrics {
 /// MONOVTX_METRICS is the MonoVtxMetrics object which stores the metrics
 static MONOVTX_METRICS: OnceLock<MonoVtxMetrics> = OnceLock::new();
 
-// forward_metrics is a helper function used to fetch the
+// monovertex_metrics is a helper function used to fetch the
 // MonoVtxMetrics object
 pub(crate) fn monovertex_metrics() -> &'static MonoVtxMetrics {
     MONOVTX_METRICS.get_or_init(MonoVtxMetrics::new)
@@ -575,31 +807,61 @@ pub(crate) fn mvtx_forward_metric_labels() -> &'static Vec<(String, String)> {
     })
 }
 
-static PIPELINE_READ_METRICS_LABELS: OnceLock<Vec<(String, String)>> = OnceLock::new();
+static PIPELINE_METRIC_LABELS: OnceLock<Vec<(String, String)>> = OnceLock::new();
 
-pub(crate) fn pipeline_forward_metric_labels(vertex_type: &str) -> &'static Vec<(String, String)> {
-    PIPELINE_READ_METRICS_LABELS.get_or_init(|| {
+pub(crate) fn pipeline_metric_labels(vertex_type: &str) -> &'static Vec<(String, String)> {
+    PIPELINE_METRIC_LABELS.get_or_init(|| {
         vec![
             (
-                PIPELINE_NAME_LABEL.to_string(),
-                get_pipeline_name().to_string(),
+                PIPELINE_VERTEX_LABEL.to_string(),
+                get_vertex_name().to_string(),
             ),
             (
-                PIPELINE_REPLICA_LABEL.to_string(),
-                get_vertex_replica().to_string(),
+                PIPELINE_NAME_LABEL.to_string(),
+                get_pipeline_name().to_string(),
             ),
             (
                 PIPELINE_VERTEX_TYPE_LABEL.to_string(),
                 vertex_type.to_string(),
             ),
             (
-                PIPELINE_VERTEX_LABEL.to_string(),
-                get_vertex_name().to_string(),
+                PIPELINE_REPLICA_LABEL.to_string(),
+                get_vertex_replica().to_string(),
             ),
         ]
     })
 }
 
+pub(crate) fn pipeline_drop_metric_labels(
+    vertex_type: &str,
+    partition_name: &str,
+    reason: &str,
+) -> Vec<(String, String)> {
+    vec![
+        (
+            PIPELINE_VERTEX_LABEL.to_string(),
+            get_vertex_name().to_string(),
+        ),
+        (
+            PIPELINE_NAME_LABEL.to_string(),
+            get_pipeline_name().to_string(),
+        ),
+        (
+            PIPELINE_VERTEX_TYPE_LABEL.to_string(),
+            vertex_type.to_string(),
+        ),
+        (
+            PIPELINE_REPLICA_LABEL.to_string(),
+            get_vertex_replica().to_string(),
+        ),
+        (
+            PIPELINE_PARTITION_NAME_LABEL.to_string(),
+            partition_name.to_string(),
+        ),
+        (PIPELINE_DROP_REASON_LABEL.to_string(), reason.to_string()),
+    ]
+}
+
 static PIPELINE_ISB_METRICS_LABELS: OnceLock<Vec<(String, String)>> = OnceLock::new();
 
 pub(crate) fn pipeline_isb_metric_labels() -> &'static Vec<(String, String)> {
@@ -678,6 +940,13 @@ async fn livez() -> impl IntoResponse {
 }
 
 async fn sidecar_livez(State(state): State<ComponentHealthChecks>) -> impl IntoResponse {
+    // Check if health checks are disabled via the environment variable
+    if env::var("NUMAFLOW_HEALTH_CHECK_DISABLED")
+        .map(|v| v.eq_ignore_ascii_case("true"))
+        .unwrap_or(false)
+    {
+        return StatusCode::NO_CONTENT;
+    }
     match state {
         ComponentHealthChecks::Monovertex(mut monovertex_state) => {
             // this call also check the health of transformer if it is configured in the Source.
@@ -710,6 +979,12 @@ async fn sidecar_livez(State(state): State<ComponentHealthChecks>) -> impl IntoR
                     return StatusCode::INTERNAL_SERVER_ERROR;
                 }
             }
+            PipelineComponents::Reduce(mut reducer) => {
+                if !reducer.ready().await {
+                    error!("Pipeline reduce component is not ready");
+                    return StatusCode::INTERNAL_SERVER_ERROR;
+                }
+            }
         },
     }
     StatusCode::NO_CONTENT
@@ -901,6 +1176,8 @@ async fn expose_pending_metrics_(
     is_mono_vertex: bool,
 ) {
     let mut ticker = time::interval(lag_checking_interval);
+    // print pending messages every one minute
+    let mut last_logged = std::time::Instant::now();
 
     loop {
         ticker.tick().await;
@@ -909,7 +1186,12 @@ async fn expose_pending_metrics_(
             LagReader::Source(source) => match fetch_source_pending(source).await {
                 Ok(pending) => {
                     if pending != -1 {
-                        info!("Pending messages {:?}", pending);
+                        if last_logged.elapsed().as_secs() >= 60 {
+                            info!("Pending messages {:?}", pending);
+                            last_logged = std::time::Instant::now();
+                        } else {
+                            debug!("Pending messages {:?}", pending);
+                        }
                         if is_mono_vertex {
                             let metric_labels = mvtx_forward_metric_labels().clone();
                             monovertex_metrics()
@@ -918,10 +1200,10 @@ async fn expose_pending_metrics_(
                                 .set(pending);
                         } else {
                             let mut metric_labels =
-                                pipeline_forward_metric_labels("source").clone();
+                                pipeline_metric_labels(VERTEX_TYPE_SOURCE).clone();
                             metric_labels.push((
                                 PIPELINE_PARTITION_NAME_LABEL.to_string(),
-                                "source".to_string(),
+                                "Source".to_string(),
                             ));
                             pipeline_metrics()
                                 .pending_raw
@@ -939,9 +1221,14 @@ async fn expose_pending_metrics_(
                     match fetch_isb_pending(reader).await {
                         Ok(pending) => {
                             if pending != -1 {
-                                info!("Pending messages {:?}", pending);
+                                if last_logged.elapsed().as_secs() >= 60 {
+                                    info!(partition=?reader.name(), "Pending messages {:?}", pending);
+                                    last_logged = std::time::Instant::now();
+                                } else {
+                                    debug!(partition=?reader.name(), "Pending messages {:?}", pending);
+                                }
                                 let mut metric_labels =
-                                    pipeline_forward_metric_labels(reader.name()).clone();
+                                    pipeline_metric_labels(reader.name()).clone();
                                 metric_labels.push((
                                     PIPELINE_PARTITION_NAME_LABEL.to_string(),
                                     reader.name().to_string(),
@@ -1062,6 +1349,8 @@ async fn expose_pending_metrics(
         ("15m", 900),
     ];
 
+    let mut last_logged = std::time::Instant::now();
+
     loop {
         ticker.tick().await;
         for (name, pending_stats) in pending_map.lock().await.iter() {
@@ -1077,7 +1366,7 @@ async fn expose_pending_metrics(
                             .get_or_create(&metric_labels)
                             .set(pending);
                     } else {
-                        let mut metric_labels = pipeline_forward_metric_labels(name).clone();
+                        let mut metric_labels = pipeline_metric_labels(name).clone();
                         metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string()));
                         metric_labels
                             .push((PIPELINE_PARTITION_NAME_LABEL.to_string(), name.to_string()));
@@ -1090,7 +1379,12 @@ async fn expose_pending_metrics(
             }
             // skip for those the pending is not implemented
             if !pending_info.is_empty() {
-                info!("Pending messages {:?}", pending_info);
+                if last_logged.elapsed().as_secs() >= 60 {
+                    info!("Pending messages {:?}", pending_info);
+                    last_logged = std::time::Instant::now();
+                } else {
+                    debug!("Pending messages {:?}", pending_info);
+                }
                 pending_info.clear();
             }
         }
@@ -1450,7 +1744,7 @@ mod tests {
 
         {
             for (i, (label, _)) in lookback_seconds_map.iter().enumerate() {
-                let mut metric_labels = pipeline_forward_metric_labels("stream1").clone();
+                let mut metric_labels = pipeline_metric_labels("stream1").clone();
                 metric_labels.push((PENDING_PERIOD_LABEL.to_string(), label.to_string()));
                 metric_labels.push((
                     PIPELINE_PARTITION_NAME_LABEL.to_string(),
@@ -1462,7 +1756,7 @@ mod tests {
                     .get();
                 stored_values_stream_one[i] = guage;
 
-                let mut metric_labels = pipeline_forward_metric_labels("stream2").clone();
+                let mut metric_labels = pipeline_metric_labels("stream2").clone();
                 metric_labels.push((PENDING_PERIOD_LABEL.to_string(), (*label).to_string()));
                 metric_labels.push((
                     PIPELINE_PARTITION_NAME_LABEL.to_string(),
diff --git a/rust/numaflow-core/src/pipeline.rs b/rust/numaflow-core/src/pipeline.rs
index 0c8fc844..8cf1b56c 100644
--- a/rust/numaflow-core/src/pipeline.rs
+++ b/rust/numaflow-core/src/pipeline.rs
@@ -7,17 +7,29 @@ use serving::callback::CallbackHandler;
 use tokio_util::sync::CancellationToken;
 use tracing::info;
 
+use crate::config::components::reduce::WindowType;
 use crate::config::is_mono_vertex;
 use crate::config::pipeline;
 use crate::config::pipeline::isb::Stream;
 use crate::config::pipeline::map::MapVtxConfig;
 use crate::config::pipeline::watermark::WatermarkConfig;
-use crate::config::pipeline::{PipelineConfig, ServingStoreType, SinkVtxConfig, SourceVtxConfig};
+use crate::config::pipeline::{
+    PipelineConfig, ReduceVtxConfig, ServingStoreType, SinkVtxConfig, SourceVtxConfig,
+};
 use crate::metrics::{ComponentHealthChecks, LagReader, PendingReaderTasks, PipelineComponents};
+use crate::pipeline::forwarder::reduce_forwarder::ReduceForwarder;
 use crate::pipeline::forwarder::source_forwarder;
 use crate::pipeline::isb::jetstream::reader::JetStreamReader;
 use crate::pipeline::isb::jetstream::writer::JetstreamWriter;
 use crate::pipeline::pipeline::isb::BufferReaderConfig;
+use crate::reduce::pbq::PBQBuilder;
+use crate::reduce::reducer::aligned::reducer::AlignedReducer;
+use crate::reduce::reducer::aligned::windower::WindowManager;
+use crate::reduce::reducer::aligned::windower::fixed::FixedWindowManager;
+use crate::reduce::reducer::aligned::windower::sliding::SlidingWindowManager;
+use crate::reduce::wal::segment::WalType;
+use crate::reduce::wal::segment::append::AppendOnlyWal;
+use crate::reduce::wal::segment::compactor::{Compactor, WindowKind};
 use crate::shared::create_components;
 use crate::shared::metrics::start_metrics_server;
 use crate::sink::serve::ServingStore;
@@ -69,61 +81,15 @@ pub(crate) async fn start_forwarder(
         }
         pipeline::VertexType::Sink(sink) => {
             info!("Starting sink forwarder");
-
-            // create watermark handle, if watermark is enabled
-            let edge_watermark_handle = match &config.watermark_config {
-                Some(WatermarkConfig::Edge(edge_config)) => Some(
-                    ISBWatermarkHandle::new(
-                        config.vertex_name,
-                        config.replica,
-                        config.read_timeout,
-                        js_context.clone(),
-                        edge_config,
-                        &config.to_vertex_config,
-                        cln_token.clone(),
-                    )
-                    .await?,
-                ),
-                _ => None,
-            };
-
-            start_sink_forwarder(
-                cln_token,
-                js_context,
-                config.clone(),
-                sink.clone(),
-                edge_watermark_handle,
-            )
-            .await?;
+            start_sink_forwarder(cln_token, js_context, config.clone(), sink.clone()).await?;
         }
         pipeline::VertexType::Map(map) => {
             info!("Starting map forwarder");
-
-            // create watermark handle, if watermark is enabled
-            let edge_watermark_handle = match &config.watermark_config {
-                Some(WatermarkConfig::Edge(edge_config)) => Some(
-                    ISBWatermarkHandle::new(
-                        config.vertex_name,
-                        config.replica,
-                        config.read_timeout,
-                        js_context.clone(),
-                        edge_config,
-                        &config.to_vertex_config,
-                        cln_token.clone(),
-                    )
-                    .await?,
-                ),
-                _ => None,
-            };
-
-            start_map_forwarder(
-                cln_token,
-                js_context,
-                config.clone(),
-                map.clone(),
-                edge_watermark_handle,
-            )
-            .await?;
+            start_map_forwarder(cln_token, js_context, config.clone(), map.clone()).await?;
+        }
+        pipeline::VertexType::Reduce(reduce) => {
+            info!("Starting reduce forwarder");
+            start_reduce_forwarder(cln_token, js_context, config.clone(), reduce.clone()).await?;
         }
     }
     Ok(())
@@ -157,6 +123,7 @@ async fn start_source_forwarder(
         tracker_handle.clone(),
         cln_token.clone(),
         source_watermark_handle.clone().map(WatermarkHandle::Source),
+        config.vertex_type_config.to_string(),
     )
     .await;
 
@@ -208,8 +175,12 @@ async fn start_map_forwarder(
     js_context: Context,
     config: PipelineConfig,
     map_vtx_config: MapVtxConfig,
-    watermark_handle: Option<ISBWatermarkHandle>,
 ) -> Result<()> {
+    // create watermark handle, if watermark is enabled
+    let watermark_handle =
+        create_components::create_edge_watermark_handle(&config, &js_context, &cln_token, None)
+            .await?;
+
     // Only the reader config of the first "from" vertex is needed, as all "from" vertices currently write
     // to a common buffer, in the case of a join.
     let reader_config = &config
@@ -247,6 +218,7 @@ async fn start_map_forwarder(
         tracker_handle.clone(),
         cln_token.clone(),
         watermark_handle.clone().map(WatermarkHandle::ISB),
+        config.vertex_type_config.to_string(),
     )
     .await;
 
@@ -321,13 +293,188 @@ async fn start_map_forwarder(
     Ok(())
 }
 
+async fn start_reduce_forwarder(
+    cln_token: CancellationToken,
+    js_context: Context,
+    config: PipelineConfig,
+    reduce_vtx_config: ReduceVtxConfig,
+) -> Result<()> {
+    let window_manager = match &reduce_vtx_config.reducer_config.window_config.window_type {
+        WindowType::Fixed(fixed_config) => {
+            WindowManager::Fixed(FixedWindowManager::new(fixed_config.length))
+        }
+        WindowType::Sliding(sliding_config) => {
+            // sliding window needs to save state if WAL is configured to avoid duplicate processing
+            // since a message can be part of multiple windows.
+            let state_file_path =
+                if let Some(storage_config) = &reduce_vtx_config.wal_storage_config {
+                    let mut path = storage_config.path.clone();
+                    path.push(format!("{}-window.state", config.vertex_name));
+                    Some(path)
+                } else {
+                    None
+                };
+
+            WindowManager::Sliding(SlidingWindowManager::new(
+                sliding_config.length,
+                sliding_config.slide,
+                state_file_path,
+            ))
+        }
+        WindowType::Session(_) | WindowType::Accumulator(_) => {
+            panic!("Session and Accumulator windows are not supported yet");
+        }
+    };
+
+    // create watermark handle, if watermark is enabled
+    let watermark_handle = create_components::create_edge_watermark_handle(
+        &config,
+        &js_context,
+        &cln_token,
+        Some(window_manager.clone()),
+    )
+    .await?;
+
+    let reader_config = &config
+        .from_vertex_config
+        .first()
+        .ok_or_else(|| error::Error::Config("No from vertex config found".to_string()))?
+        .reader_config;
+
+    // reduce pod always reads from a single stream (pod per partition)
+    let stream = reader_config
+        .streams
+        .first()
+        .cloned()
+        .ok_or_else(|| error::Error::Config("No stream found for reduce vertex".to_string()))?;
+
+    // we don't need to pass the watermark handle to the tracker because in reduce windower is
+    // responsible for identifying the lowest watermark in the pod.
+    let tracker_handle = TrackerHandle::new(None, None);
+    // Create buffer reader
+    let buffer_reader = create_buffer_reader(
+        config.vertex_type_config.to_string(),
+        stream,
+        reader_config.clone(),
+        js_context.clone(),
+        tracker_handle.clone(),
+        config.batch_size,
+        watermark_handle.clone(),
+    )
+    .await?;
+
+    // Create buffer writer
+    let buffer_writer = create_buffer_writer(
+        &config,
+        js_context.clone(),
+        tracker_handle.clone(),
+        cln_token.clone(),
+        watermark_handle.clone().map(WatermarkHandle::ISB),
+        config.vertex_type_config.to_string(),
+    )
+    .await;
+
+    // Create user-defined aligned reducer client
+    let reducer_client =
+        create_components::create_aligned_reducer(reduce_vtx_config.reducer_config.clone()).await?;
+
+    // Create WAL if configured
+    let (wal, gc_wal) = if let Some(storage_config) = &reduce_vtx_config.wal_storage_config {
+        let wal_path = storage_config.path.clone();
+
+        let append_only_wal = AppendOnlyWal::new(
+            WalType::Data,
+            wal_path.clone(),
+            storage_config.max_file_size_mb,
+            storage_config.flush_interval_ms,
+            storage_config.channel_buffer_size,
+            storage_config.max_segment_age_secs,
+        )
+        .await?;
+
+        let compactor = Compactor::new(
+            wal_path.clone(),
+            WindowKind::Aligned,
+            storage_config.max_file_size_mb,
+            storage_config.flush_interval_ms,
+            storage_config.channel_buffer_size,
+            storage_config.max_segment_age_secs,
+        )
+        .await?;
+
+        let gc_wal = AppendOnlyWal::new(
+            WalType::Gc,
+            wal_path,
+            storage_config.max_file_size_mb,
+            storage_config.flush_interval_ms,
+            storage_config.channel_buffer_size,
+            storage_config.max_segment_age_secs,
+        )
+        .await?;
+
+        (
+            Some(crate::reduce::pbq::WAL {
+                append_only_wal,
+                compactor,
+            }),
+            Some(gc_wal),
+        )
+    } else {
+        (None, None)
+    };
+
+    // Create PBQ
+    let pbq_builder = PBQBuilder::new(buffer_reader.clone(), tracker_handle.clone());
+    let pbq = if let Some(wal) = wal {
+        pbq_builder.wal(wal).build()
+    } else {
+        pbq_builder.build()
+    };
+
+    let pending_reader = shared::metrics::create_pending_reader(
+        &config.metrics_config,
+        LagReader::ISB(vec![buffer_reader]),
+    )
+    .await;
+    let _pending_reader_handle = pending_reader.start(is_mono_vertex()).await;
+
+    // Start the metrics server with one of the clients
+    start_metrics_server(
+        config.metrics_config.clone(),
+        ComponentHealthChecks::Pipeline(PipelineComponents::Reduce(reducer_client.clone())),
+    )
+    .await;
+
+    let reducer = AlignedReducer::new(
+        reducer_client,
+        window_manager,
+        buffer_writer,
+        gc_wal,
+        reduce_vtx_config
+            .reducer_config
+            .window_config
+            .allowed_lateness,
+    )
+    .await;
+
+    let forwarder = ReduceForwarder::new(pbq, reducer);
+    forwarder.start(cln_token).await?;
+
+    info!("Reduce forwarder has stopped successfully");
+    Ok(())
+}
+
 async fn start_sink_forwarder(
     cln_token: CancellationToken,
     js_context: Context,
     config: PipelineConfig,
     sink: SinkVtxConfig,
-    watermark_handle: Option<ISBWatermarkHandle>,
 ) -> Result<()> {
+    // create watermark handle, if watermark is enabled
+    let watermark_handle =
+        create_components::create_edge_watermark_handle(&config, &js_context, &cln_token, None)
+            .await?;
+
     // Only the reader config of the first "from" vertex is needed, as all "from" vertices currently write
     // to a common buffer, in the case of a join.
     let reader_config = &config
@@ -446,6 +593,7 @@ async fn create_buffer_writer(
     tracker_handle: TrackerHandle,
     cln_token: CancellationToken,
     watermark_handle: Option<WatermarkHandle>,
+    vertex_type: String,
 ) -> JetstreamWriter {
     JetstreamWriter::new(
         config.to_vertex_config.clone(),
@@ -454,6 +602,7 @@ async fn create_buffer_writer(
         tracker_handle,
         cln_token,
         watermark_handle,
+        vertex_type,
     )
 }
 
@@ -716,6 +865,7 @@ mod tests {
                 },
                 headers: HashMap::new(),
                 metadata: None,
+                is_late: false,
             };
             let message: bytes::BytesMut = message.try_into().unwrap();
 
@@ -912,6 +1062,7 @@ mod tests {
                 },
                 headers: HashMap::new(),
                 metadata: None,
+                is_late: false,
             };
             let message: bytes::BytesMut = message.try_into().unwrap();
 
diff --git a/rust/numaflow-core/src/pipeline/forwarder.rs b/rust/numaflow-core/src/pipeline/forwarder.rs
index 86ce982b..6012708c 100644
--- a/rust/numaflow-core/src/pipeline/forwarder.rs
+++ b/rust/numaflow-core/src/pipeline/forwarder.rs
@@ -44,6 +44,7 @@ pub(crate) mod sink_forwarder;
 /// Write is ISB.
 pub(crate) mod map_forwarder;
 
+pub(crate) mod reduce_forwarder;
 /// Source where the Reader is builtin or User-defined Source, Write is ISB,
 /// with an optional Transformer.
 pub(crate) mod source_forwarder;
diff --git a/rust/numaflow-core/src/pipeline/forwarder/reduce_forwarder.rs b/rust/numaflow-core/src/pipeline/forwarder/reduce_forwarder.rs
new file mode 100644
index 00000000..93069d52
--- /dev/null
+++ b/rust/numaflow-core/src/pipeline/forwarder/reduce_forwarder.rs
@@ -0,0 +1,57 @@
+use tokio_util::sync::CancellationToken;
+use tracing::{error, info};
+
+use crate::Result;
+use crate::error::Error;
+use crate::reduce::pbq::PBQ;
+use crate::reduce::reducer::aligned::reducer::AlignedReducer;
+
+/// ReduceForwarder is a component which starts a PBQ reader and a reducer
+/// and manages the lifecycle of these components.
+pub(crate) struct ReduceForwarder {
+    pbq: PBQ,
+    reducer: AlignedReducer,
+}
+
+impl ReduceForwarder {
+    pub(crate) fn new(pbq: PBQ, reducer: AlignedReducer) -> Self {
+        Self { pbq, reducer }
+    }
+
+    pub(crate) async fn start(self, cln_token: CancellationToken) -> Result<()> {
+        let child_token = cln_token.child_token();
+
+        // Start the PBQ reader
+        let (read_messages_stream, pbq_handle) =
+            self.pbq.streaming_read(child_token.clone()).await?;
+
+        // Start the reducer
+        let processor_handle = self
+            .reducer
+            .start(read_messages_stream, child_token)
+            .await?;
+
+        // Join the pbq and reducer
+        let (pbq_result, processor_result) = tokio::try_join!(pbq_handle, processor_handle)
+            .map_err(|e| {
+                error!(?e, "Error while joining PBQ reader and reducer");
+                Error::Forwarder(format!(
+                    "Error while joining PBQ reader and reducer: {:?}",
+                    e
+                ))
+            })?;
+
+        processor_result.inspect_err(|e| {
+            error!(?e, "Error in reducer");
+            cln_token.cancel();
+        })?;
+
+        pbq_result.inspect_err(|e| {
+            error!(?e, "Error in PBQ reader");
+            cln_token.cancel();
+        })?;
+
+        info!("Reduce forwarder completed successfully");
+        Ok(())
+    }
+}
diff --git a/rust/numaflow-core/src/pipeline/forwarder/source_forwarder.rs b/rust/numaflow-core/src/pipeline/forwarder/source_forwarder.rs
index 028486b2..4f5cb28b 100644
--- a/rust/numaflow-core/src/pipeline/forwarder/source_forwarder.rs
+++ b/rust/numaflow-core/src/pipeline/forwarder/source_forwarder.rs
@@ -162,7 +162,7 @@ mod tests {
         }
     }
 
-    // #[cfg(feature = "nats-tests")]
+    #[cfg(feature = "nats-tests")]
     #[tokio::test]
     async fn test_source_forwarder() {
         let tracker_handle = TrackerHandle::new(None, None);
@@ -277,6 +277,7 @@ mod tests {
             tracker_handle.clone(),
             cln_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         // create the forwarder with the source, transformer, and writer
diff --git a/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs b/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs
index 181bcb5a..cca03e0a 100644
--- a/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs
+++ b/rust/numaflow-core/src/pipeline/isb/jetstream/reader.rs
@@ -7,6 +7,7 @@ use async_nats::jetstream::{
 };
 use backoff::retry::Retry;
 use backoff::strategy::fixed;
+use chrono::Utc;
 use prost::Message as ProtoMessage;
 use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc, oneshot};
 use tokio::task::JoinHandle;
@@ -17,17 +18,15 @@ use tokio_util::sync::CancellationToken;
 use tracing::warn;
 use tracing::{error, info};
 
+use crate::Result;
 use crate::config::get_vertex_name;
 use crate::config::pipeline::isb::{BufferReaderConfig, Stream};
 use crate::error::Error;
 use crate::message::{IntOffset, Message, MessageID, MessageType, Metadata, Offset, ReadAck};
-use crate::metrics::{
-    pipeline_forward_metric_labels, pipeline_isb_metric_labels, pipeline_metrics,
-};
+use crate::metrics::{PIPELINE_PARTITION_NAME_LABEL, pipeline_metric_labels, pipeline_metrics};
 use crate::shared::grpc::utc_from_timestamp;
 use crate::tracker::TrackerHandle;
 use crate::watermark::isb::ISBWatermarkHandle;
-use crate::{Result, metrics};
 
 const ACK_RETRY_INTERVAL: u64 = 100;
 const ACK_RETRY_ATTEMPTS: usize = usize::MAX;
@@ -113,6 +112,7 @@ impl TryFrom<JSWrappedMessage> for Message {
                     .ok_or(Error::Proto("Missing id".to_string()))?
                     .vertex_name,
             }),
+            is_late: message_info.is_late,
         })
     }
 }
@@ -165,20 +165,23 @@ impl JetStreamReader {
     /// cancellationToken cancellation during the permit reservation and fetching messages,
     /// since rest of the operations should finish immediately.
     pub(crate) async fn streaming_read(
-        self,
+        mut self,
         cancel_token: CancellationToken,
     ) -> Result<(ReceiverStream<Message>, JoinHandle<Result<()>>)> {
         let (messages_tx, messages_rx) = mpsc::channel(2 * self.batch_size);
 
         let handle: JoinHandle<Result<()>> = tokio::spawn({
             async move {
-                let mut labels = pipeline_forward_metric_labels(&self.vertex_type).clone();
-                let semaphore = Arc::new(Semaphore::new(MAX_ACK_PENDING));
+                let mut labels = pipeline_metric_labels(&self.vertex_type).clone();
                 labels.push((
-                    metrics::PIPELINE_PARTITION_NAME_LABEL.to_string(),
+                    PIPELINE_PARTITION_NAME_LABEL.to_string(),
                     self.stream.name.to_string(),
                 ));
 
+                let semaphore = Arc::new(Semaphore::new(MAX_ACK_PENDING));
+                let mut processed_msgs_count: usize = 0;
+                let mut last_logged_at = Instant::now();
+
                 let mut message_stream = self.consumer.messages().await.map_err(|e| {
                     Error::ISB(format!(
                         "Failed to get message stream from Jetstream: {:?}",
@@ -198,7 +201,6 @@ impl JetStreamReader {
                                 info!(stream=?self.stream, "Stream has been closed");
                                 break;
                             };
-
                             let jetstream_message = match message {
                                 Ok(message) => message,
                                 Err(e) => {
@@ -206,6 +208,7 @@ impl JetStreamReader {
                                     continue;
                                 }
                             };
+                            let message_processing_start = Instant::now();
 
                             let js_message = JSWrappedMessage {
                                 partition_idx: self.stream.partition,
@@ -226,7 +229,7 @@ impl JetStreamReader {
                                 continue;
                             }
 
-                            if let Some(watermark_handle) = self.watermark_handle.as_ref() {
+                            if let Some(watermark_handle) = self.watermark_handle.as_mut() {
                                 let watermark = watermark_handle.fetch_watermark(message.offset.clone()).await;
                                 message.watermark = Some(watermark);
                             }
@@ -238,6 +241,7 @@ impl JetStreamReader {
                             // Reserve a permit before sending the message to the channel.
                             let permit = Arc::clone(&semaphore).acquire_owned().await.expect("Failed to acquire semaphore permit");
                             tokio::spawn(Self::start_work_in_progress(
+                                labels.clone(),
                                 message.offset.clone(),
                                 jetstream_message,
                                 ack_rx,
@@ -246,12 +250,46 @@ impl JetStreamReader {
                                 cancel_token.clone(),
                             ));
 
+                            let message_bytes = message.value.len();
                             messages_tx.send(message).await.expect("Failed to send message to channel");
+                            // increment read metrics for pipeline
                             pipeline_metrics()
                                 .forwarder
                                 .read_total
                                 .get_or_create(&labels)
                                 .inc();
+                            // since wmb messages are already ignored
+                            pipeline_metrics()
+                                .forwarder
+                                .data_read_total
+                                .get_or_create(&labels)
+                                .inc();
+                            pipeline_metrics()
+                                .forwarder
+                                .read_bytes_total
+                                .get_or_create(&labels)
+                                .inc_by(message_bytes as u64);
+                            pipeline_metrics()
+                                .forwarder
+                                .data_read_bytes_total
+                                .get_or_create(&labels)
+                                .inc_by(message_bytes as u64);
+                            pipeline_metrics()
+                                .forwarder
+                                .read_processing_time
+                                .get_or_create(&labels)
+                                .observe(message_processing_start.elapsed().as_micros() as f64);
+
+                            processed_msgs_count += 1;
+                            if last_logged_at.elapsed().as_secs() >= 1 {
+                                info!(
+                                    "Processed {} messages in {:?}",
+                                    processed_msgs_count,
+                                    Utc::now()
+                                );
+                                processed_msgs_count = 0;
+                                last_logged_at = Instant::now();
+                            }
                         }
                     }
                 }
@@ -272,6 +310,7 @@ impl JetStreamReader {
     // We will continuously retry if there is an error in acknowledging the message as work-in-progress.
     // If the sender end of the ack_rx channel was dropped before sending a final Ack or Nak (due to some unhandled/unknown failure), we will send a Nak to Jetstream.
     async fn start_work_in_progress(
+        labels: Vec<(String, String)>,
         offset: Offset,
         msg: JetstreamMessage,
         mut ack_rx: oneshot::Receiver<ReadAck>,
@@ -316,14 +355,14 @@ impl JetStreamReader {
 
                     pipeline_metrics()
                         .forwarder
-                        .ack_time
-                        .get_or_create(pipeline_isb_metric_labels())
+                        .ack_processing_time
+                        .get_or_create(&labels)
                         .observe(start.elapsed().as_micros() as f64);
 
                     pipeline_metrics()
                         .forwarder
                         .ack_total
-                        .get_or_create(pipeline_isb_metric_labels())
+                        .get_or_create(&labels)
                         .inc();
                     return;
                 }
@@ -357,7 +396,7 @@ impl JetStreamReader {
                 let result = match msg.ack_with(ack_kind).await {
                     Ok(_) => Ok(()),
                     Err(e) => {
-                        warn!(?e, "Failed to send {:?} to Jetstream for message", ack_kind);
+                        warn!(error = ?e, ?ack_kind, "Failed to send ack to Jetstream for message");
                         Err(Error::Connection(format!(
                             "Failed to send {:?}: {:?}",
                             ack_kind, e
@@ -407,7 +446,6 @@ impl fmt::Display for JetStreamReader {
 
 #[cfg(test)]
 mod tests {
-    use std::collections::HashMap;
     use std::sync::Arc;
 
     use async_nats::jetstream;
@@ -492,8 +530,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let message_bytes: BytesMut = message.try_into().unwrap();
             context
@@ -597,8 +634,7 @@ mod tests {
                     offset: format!("{}-0", i + 1).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             offsets.push(message.offset.clone());
             let message_bytes: BytesMut = message.try_into().unwrap();
diff --git a/rust/numaflow-core/src/pipeline/isb/jetstream/writer.rs b/rust/numaflow-core/src/pipeline/isb/jetstream/writer.rs
index af441945..eb5dd44f 100644
--- a/rust/numaflow-core/src/pipeline/isb/jetstream/writer.rs
+++ b/rust/numaflow-core/src/pipeline/isb/jetstream/writer.rs
@@ -6,7 +6,7 @@ use std::time::Duration;
 
 use async_nats::jetstream::Context;
 use async_nats::jetstream::consumer::PullConsumer;
-use async_nats::jetstream::context::PublishAckFuture;
+use async_nats::jetstream::context::{Publish, PublishAckFuture};
 use async_nats::jetstream::publish::PublishAck;
 use async_nats::jetstream::stream::RetentionPolicy::Limits;
 use bytes::BytesMut;
@@ -16,14 +16,17 @@ use tokio::time::{Instant, sleep};
 use tokio_stream::StreamExt;
 use tokio_stream::wrappers::ReceiverStream;
 use tokio_util::sync::CancellationToken;
-use tracing::{debug, error, info, warn};
+use tracing::{debug, error, warn};
 
 use crate::Result;
 use crate::config::pipeline::ToVertexConfig;
 use crate::config::pipeline::isb::{BufferFullStrategy, Stream};
 use crate::error::Error;
 use crate::message::{IntOffset, Message, Offset};
-use crate::metrics::{pipeline_isb_metric_labels, pipeline_metrics};
+use crate::metrics::{
+    PIPELINE_PARTITION_NAME_LABEL, pipeline_isb_metric_labels, pipeline_metric_labels,
+    pipeline_metrics,
+};
 use crate::shared::forward;
 use crate::tracker::TrackerHandle;
 use crate::watermark::WatermarkHandle;
@@ -50,6 +53,8 @@ pub(crate) struct JetstreamWriter {
     tracker_handle: TrackerHandle,
     sem: Arc<Semaphore>,
     watermark_handle: Option<WatermarkHandle>,
+    paf_concurrency: usize,
+    vertex_type: String,
 }
 
 impl JetstreamWriter {
@@ -62,6 +67,7 @@ impl JetstreamWriter {
         tracker_handle: TrackerHandle,
         cancel_token: CancellationToken,
         watermark_handle: Option<WatermarkHandle>,
+        vertex_type: String,
     ) -> Self {
         let to_vertex_streams = config
             .iter()
@@ -80,6 +86,8 @@ impl JetstreamWriter {
             tracker_handle,
             sem: Arc::new(Semaphore::new(paf_concurrency)),
             watermark_handle,
+            paf_concurrency,
+            vertex_type,
         };
 
         // spawn a task for checking whether buffer is_full
@@ -188,22 +196,23 @@ impl JetstreamWriter {
         cln_token: CancellationToken,
     ) -> Result<JoinHandle<Result<()>>> {
         let handle: JoinHandle<Result<()>> = tokio::spawn(async move {
-            info!("Starting streaming Jetstream writer");
             let mut messages_stream = messages_stream;
             let mut hash = DefaultHasher::new();
 
-            let mut processed_msgs_count: usize = 0;
-            let mut last_logged_at = Instant::now();
-
             while let Some(message) = messages_stream.next().await {
+                let write_processing_start = Instant::now();
                 // if message needs to be dropped, ack and continue
-                // TODO: add metric for dropped count
                 if message.dropped() {
                     // delete the entry from tracker
                     self.tracker_handle
                         .delete(message.offset)
                         .await
                         .expect("Failed to delete offset from tracker");
+                    pipeline_metrics()
+                        .forwarder
+                        .udf_drop_total
+                        .get_or_create(pipeline_metric_labels(&self.vertex_type))
+                        .inc();
                     continue;
                 }
 
@@ -239,16 +248,17 @@ impl JetstreamWriter {
                         )
                         .await
                     {
+                        let partition_name = stream.name;
+                        Self::send_write_metrics(
+                            partition_name,
+                            &self.vertex_type,
+                            message.clone(),
+                            write_processing_start,
+                        );
                         pafs.push((stream, paf));
                     }
                 }
 
-                pipeline_metrics()
-                    .forwarder
-                    .write_total
-                    .get_or_create(pipeline_isb_metric_labels())
-                    .inc();
-
                 if pafs.is_empty() {
                     continue;
                 }
@@ -259,24 +269,47 @@ impl JetstreamWriter {
                         error!(?e, "Failed to resolve PAFs");
                         cln_token.cancel();
                     })?;
-
-                processed_msgs_count += 1;
-                if last_logged_at.elapsed().as_secs() >= 1 {
-                    info!(
-                        "Processed {} messages in {:?}",
-                        processed_msgs_count,
-                        std::time::Instant::now()
-                    );
-                    processed_msgs_count = 0;
-                    last_logged_at = Instant::now();
-                }
             }
-            info!("Streaming jetstream writer finished");
+
+            // wait for all the paf resolvers to complete before returning
+            let _ = Arc::clone(&self.sem)
+                .acquire_many_owned(self.paf_concurrency as u32)
+                .await
+                .expect("Failed to acquire semaphore permit");
+
             Ok(())
         });
         Ok(handle)
     }
 
+    fn send_write_metrics(
+        partition_name: &str,
+        vertex_type: &str,
+        message: Message,
+        write_processing_start: Instant,
+    ) {
+        let mut labels = pipeline_metric_labels(vertex_type).clone();
+        labels.push((
+            PIPELINE_PARTITION_NAME_LABEL.to_string(),
+            partition_name.to_string(),
+        ));
+        pipeline_metrics()
+            .forwarder
+            .write_total
+            .get_or_create(&labels)
+            .inc();
+        pipeline_metrics()
+            .forwarder
+            .write_bytes_total
+            .get_or_create(&labels)
+            .inc_by(message.value.len() as u64);
+        pipeline_metrics()
+            .forwarder
+            .write_processing_time
+            .get_or_create(&labels)
+            .observe(write_processing_start.elapsed().as_micros() as f64);
+    }
+
     /// Writes the message to the JetStream ISB and returns a future which can be
     /// awaited to get the PublishAck. It will do infinite retries until the message
     /// gets published successfully. If it returns an error it means it is fatal error
@@ -289,6 +322,11 @@ impl JetstreamWriter {
     ) -> Option<PublishAckFuture> {
         let mut log_counter = 500u16;
         let offset = message.offset.clone();
+
+        // message id will be used for deduplication
+        let id = message.id.to_string();
+        let msg_bytes = message.value.len();
+
         let payload: BytesMut = message
             .try_into()
             .expect("message serialization should not fail");
@@ -304,7 +342,7 @@ impl JetstreamWriter {
                 Some(true) => {
                     // FIXME: add metrics
                     if log_counter >= 500 {
-                        warn!(stream=?stream, "stream is full (throttled logging)");
+                        warn!(?stream, "stream is full (throttled logging)");
                         log_counter = 0;
                     }
                     log_counter += 1;
@@ -315,6 +353,18 @@ impl JetstreamWriter {
                                 .delete(offset.clone())
                                 .await
                                 .expect("Failed to delete offset from tracker");
+                            // increment drop metric if buffer is full and
+                            // Buffer full strategy is DiscardLatest
+                            pipeline_metrics()
+                                .forwarder
+                                .drop_total
+                                .get_or_create(pipeline_isb_metric_labels())
+                                .inc();
+                            pipeline_metrics()
+                                .forwarder
+                                .drop_bytes_total
+                                .get_or_create(pipeline_isb_metric_labels())
+                                .inc_by(msg_bytes as u64);
                             return None;
                         }
                         BufferFullStrategy::RetryUntilSuccess => {}
@@ -322,7 +372,12 @@ impl JetstreamWriter {
                 }
                 Some(false) => match self
                     .js_ctx
-                    .publish(stream.name, payload.clone().freeze())
+                    .send_publish(
+                        stream.name,
+                        Publish::build()
+                            .payload(payload.clone().freeze())
+                            .message_id(&id),
+                    )
                     .await
                 {
                     Ok(paf) => break paf,
@@ -359,7 +414,7 @@ impl JetstreamWriter {
             .await
             .map_err(|_e| Error::ISB("Failed to acquire semaphore permit".to_string()))?;
 
-        let this = self.clone();
+        let mut this = self.clone();
 
         tokio::spawn(async move {
             let _permit = permit;
@@ -383,8 +438,9 @@ impl JetstreamWriter {
                     Ok(ack) => {
                         if ack.duplicate {
                             warn!(
-                                stream = ?stream,
-                                ack = ?ack,
+                                message_id = ?message.id,
+                                ?stream,
+                                ?ack,
                                 "Duplicate message detected"
                             );
                         }
@@ -407,7 +463,7 @@ impl JetstreamWriter {
 
             // now the pafs have resolved, lets use the offsets to send watermark
             for (stream, offset) in offsets {
-                if let Some(watermark_handle) = this.watermark_handle.as_ref() {
+                if let Some(watermark_handle) = this.watermark_handle.as_mut() {
                     JetstreamWriter::publish_watermark(watermark_handle, stream, offset, &message)
                         .await;
                 }
@@ -477,7 +533,7 @@ impl JetstreamWriter {
 
     /// publishes the watermark for the given stream and offset
     async fn publish_watermark(
-        watermark_handle: &WatermarkHandle,
+        watermark_handle: &mut WatermarkHandle,
         stream: Stream,
         offset: Offset,
         message: &Message,
@@ -501,7 +557,6 @@ impl JetstreamWriter {
 
 #[cfg(test)]
 mod tests {
-    use std::collections::HashMap;
     use std::time::Instant;
 
     use async_nats::jetstream;
@@ -565,6 +620,7 @@ mod tests {
             tracker_handle,
             cln_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         let message = Message {
@@ -580,8 +636,7 @@ mod tests {
                 offset: "offset_0".to_string().into(),
                 index: 0,
             },
-            headers: HashMap::new(),
-            metadata: None,
+            ..Default::default()
         };
 
         let paf = writer
@@ -643,8 +698,7 @@ mod tests {
                 offset: "offset_0".to_string().into(),
                 index: 0,
             },
-            headers: HashMap::new(),
-            metadata: None,
+            ..Default::default()
         };
 
         let writer = JetstreamWriter::new(
@@ -662,6 +716,7 @@ mod tests {
             TrackerHandle::new(None, None),
             cln_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         let result = writer
@@ -726,6 +781,7 @@ mod tests {
             tracker_handle,
             cancel_token.clone(),
             None,
+            "Map".to_string(),
         );
 
         let mut result_receivers = Vec::new();
@@ -743,9 +799,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             };
             let paf = writer
                 .write(
@@ -773,8 +827,7 @@ mod tests {
                 offset: "offset_11".to_string().into(),
                 index: 11,
             },
-            headers: HashMap::new(),
-            metadata: None,
+            ..Default::default()
         };
         let paf = writer
             .write(
@@ -937,6 +990,7 @@ mod tests {
             tracker_handle,
             cancel_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         let mut js_writer = writer.clone();
@@ -1029,6 +1083,7 @@ mod tests {
             tracker_handle.clone(),
             cln_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         let (messages_tx, messages_rx) = tokio::sync::mpsc::channel(500);
@@ -1048,8 +1103,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
             tracker_handle.insert(&message, ack_tx).await.unwrap();
@@ -1122,6 +1176,7 @@ mod tests {
             tracker_handle.clone(),
             cancel_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         let (tx, rx) = tokio::sync::mpsc::channel(500);
@@ -1141,8 +1196,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
             tracker_handle.insert(&message, ack_tx).await.unwrap();
@@ -1171,8 +1225,7 @@ mod tests {
                 offset: "offset_101".to_string().into(),
                 index: 101,
             },
-            headers: HashMap::new(),
-            metadata: None,
+            ..Default::default()
         };
         let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
         tracker_handle.insert(&message, ack_tx).await.unwrap();
@@ -1267,6 +1320,7 @@ mod tests {
             tracker_handle.clone(),
             cln_token.clone(),
             None,
+            "Source".to_string(),
         );
 
         let (messages_tx, messages_rx) = tokio::sync::mpsc::channel(500);
@@ -1285,8 +1339,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let (ack_tx, ack_rx) = tokio::sync::oneshot::channel();
             tracker_handle.insert(&message, ack_tx).await.unwrap();
diff --git a/rust/numaflow-core/src/reduce.rs b/rust/numaflow-core/src/reduce.rs
index 1e8b11a8..5ebd8c05 100644
--- a/rust/numaflow-core/src/reduce.rs
+++ b/rust/numaflow-core/src/reduce.rs
@@ -2,8 +2,13 @@
 /// Write Head Log for storing reduce data until the reduction is complete.
 pub(crate) mod wal;
 
+/// All the errors Reduce could face.
 mod error;
+
 /// Persistent Buffer Queue (PBQ) for storing reduce data until the reduction is complete. It is
 /// responsible for reading from ISB and persisting the data to WAL. It is also responsible for
 /// reading the data it read from ISB or WAL (during replay) to Process and Forward (PNF).
 pub(crate) mod pbq;
+
+/// Reducer for processing the reduce data and forwarding it to the next vertex.
+pub(crate) mod reducer;
diff --git a/rust/numaflow-core/src/reduce/pbq.rs b/rust/numaflow-core/src/reduce/pbq.rs
index beb9762d..a0ccd3a9 100644
--- a/rust/numaflow-core/src/reduce/pbq.rs
+++ b/rust/numaflow-core/src/reduce/pbq.rs
@@ -5,17 +5,21 @@ use crate::reduce::wal::WalMessage;
 use crate::reduce::wal::segment::append::{AppendOnlyWal, SegmentWriteMessage};
 use crate::reduce::wal::segment::compactor::Compactor;
 use crate::tracker::TrackerHandle;
+use std::time::Duration;
 use tokio::sync::mpsc::{self, Sender};
 use tokio::task::JoinHandle;
 use tokio_stream::StreamExt;
 use tokio_stream::wrappers::ReceiverStream;
 use tokio_util::sync::CancellationToken;
+use tracing::info;
 
 /// WAL for storing the data. If None, we will not persist the data.
 #[allow(clippy::upper_case_acronyms)]
 pub(crate) struct WAL {
-    append_only_wal: AppendOnlyWal,
-    compactor: Compactor,
+    /// Segment WAL which is only for appending.
+    pub(crate) append_only_wal: AppendOnlyWal,
+    /// Compactor for compacting the Segment WAL with GC WAL.
+    pub(crate) compactor: Compactor,
 }
 
 /// PBQBuilder is a builder for PBQ.
@@ -70,10 +74,44 @@ impl PBQ {
         };
 
         let handle = tokio::spawn(async move {
-            // Replay messages from WAL
-            Self::replay_wal(wal.compactor, &tx).await?;
+            let start = std::time::Instant::now();
+            // Create a channel for WAL replay
+            let (wal_tx, mut wal_rx) = mpsc::channel(500);
+
+            // Clone the tx for use in the replay handler
+            let messages_tx = tx.clone();
+
+            // starts the compaction process, for the first time it compacts and replays the
+            // unprocessed data then it does the periodic compaction.
+            let compaction_handle = wal
+                .compactor
+                .start_compaction_with_replay(
+                    wal_tx,
+                    Duration::from_secs(60),
+                    cancellation_token.clone(),
+                )
+                .await?;
+
+            let mut replayed_count = 0;
+
+            // Process replayed messages
+            while let Some(msg) = wal_rx.recv().await {
+                let msg: WalMessage = msg.try_into().expect("Failed to parse WAL message");
+                messages_tx
+                    .send(msg.into())
+                    .await
+                    .expect("Receiver dropped");
+                replayed_count += 1;
+            }
+
+            info!(
+                time_taken_ms = start.elapsed().as_millis(),
+                ?replayed_count,
+                "Finished replaying from WAL, starting to read from ISB"
+            );
 
-            // Read from ISB and write to WAL
+            // After replaying the unprocessed data, start reading the new set of messages from ISB
+            // and also persist them in WAL.
             Self::read_isb_and_write_wal(
                 self.isb_reader,
                 wal.append_only_wal,
@@ -83,26 +121,16 @@ impl PBQ {
             )
             .await?;
 
+            // Wait for compaction task to exit gracefully
+            compaction_handle.await.expect("task failed")?;
+
+            info!("PBQ streaming read completed");
             Ok(())
         });
 
         Ok((ReceiverStream::new(rx), handle))
     }
 
-    /// Replays messages from the WAL converts them to [crate::message::Message] and sends them to
-    /// the tx channel.
-    async fn replay_wal(compactor: Compactor, tx: &Sender<Message>) -> Result<()> {
-        let (wal_tx, mut wal_rx) = mpsc::channel(100);
-        compactor.compact_with_replay(wal_tx).await?;
-
-        while let Some(msg) = wal_rx.recv().await {
-            let msg: WalMessage = msg.try_into().unwrap();
-            tx.send(msg.into()).await.expect("Receiver dropped");
-        }
-
-        Ok(())
-    }
-
     /// Reads from ISB and writes to WAL.
     async fn read_isb_and_write_wal(
         isb_reader: JetStreamReader,
@@ -144,9 +172,11 @@ impl PBQ {
             tx.send(msg).await.expect("Receiver dropped");
         }
 
+        isb_handle.await.expect("task failed")?;
+
+        // drop the sender to signal the wal eof and wait for the wal task to exit gracefully
         drop(wal_tx);
         wal_handle.await.expect("task failed")?;
-        isb_handle.await.expect("task failed")?;
 
         Ok(())
     }
@@ -164,7 +194,6 @@ mod tests {
     use async_nats::jetstream::{consumer, stream};
     use bytes::BytesMut;
     use chrono::Utc;
-    use std::collections::HashMap;
     use std::sync::Arc;
     use std::time::Duration;
 
@@ -244,8 +273,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let message_bytes: BytesMut = message.try_into().unwrap();
             context
@@ -337,11 +365,12 @@ mod tests {
             10,  // 10MB max file size
             100, // 100ms flush interval
             100, // channel buffer
+            300, // max_segment_age_secs
         )
         .await
         .unwrap();
 
-        let compactor = Compactor::new(wal_path.clone(), WindowKind::Aligned, 10, 100, 100)
+        let compactor = Compactor::new(wal_path.clone(), WindowKind::Aligned, 10, 100, 100, 300)
             .await
             .unwrap();
 
@@ -378,8 +407,7 @@ mod tests {
                     offset: format!("offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let message_bytes: BytesMut = message.try_into().unwrap();
             context
@@ -402,9 +430,10 @@ mod tests {
         reader_cancel_token.cancel();
         handle.await.unwrap().unwrap();
 
-        let append_only_wal = AppendOnlyWal::new(WalType::Data, wal_path.clone(), 10, 100, 100)
-            .await
-            .unwrap();
+        let append_only_wal =
+            AppendOnlyWal::new(WalType::Data, wal_path.clone(), 10, 100, 100, 300)
+                .await
+                .unwrap();
 
         let (tx, rx) = mpsc::channel::<SegmentWriteMessage>(10);
         let (_result_rx, writer_handle) = append_only_wal
@@ -498,9 +527,10 @@ mod tests {
             .unwrap();
 
         // First, write some messages directly to WAL
-        let append_only_wal = AppendOnlyWal::new(WalType::Data, wal_path.clone(), 10, 100, 100)
-            .await
-            .unwrap();
+        let append_only_wal =
+            AppendOnlyWal::new(WalType::Data, wal_path.clone(), 10, 100, 100, 300)
+                .await
+                .unwrap();
 
         let (tx, rx) = mpsc::channel::<SegmentWriteMessage>(100);
         let (_result_rx, writer_handle) = append_only_wal
@@ -523,8 +553,7 @@ mod tests {
                     offset: format!("wal_offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
 
             let wal_message = WalMessage {
@@ -562,11 +591,12 @@ mod tests {
         .unwrap();
 
         // Create new WAL components for PBQ
-        let append_only_wal = AppendOnlyWal::new(WalType::Data, wal_path.clone(), 10, 100, 100)
-            .await
-            .unwrap();
+        let append_only_wal =
+            AppendOnlyWal::new(WalType::Data, wal_path.clone(), 10, 100, 100, 300)
+                .await
+                .unwrap();
 
-        let compactor = Compactor::new(wal_path.clone(), WindowKind::Aligned, 10, 100, 100)
+        let compactor = Compactor::new(wal_path.clone(), WindowKind::Aligned, 10, 100, 100, 300)
             .await
             .unwrap();
 
@@ -633,8 +663,7 @@ mod tests {
                     offset: format!("isb_offset_{}", i).into(),
                     index: i as i32,
                 },
-                headers: HashMap::new(),
-                metadata: None,
+                ..Default::default()
             };
             let message_bytes: BytesMut = message.try_into().unwrap();
             context
diff --git a/rust/numaflow-core/src/reduce/reducer.rs b/rust/numaflow-core/src/reduce/reducer.rs
new file mode 100644
index 00000000..3b20329b
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer.rs
@@ -0,0 +1,58 @@
+//! Reduce module contains the implementation for Reduce operation.
+//! ```text
+//!                                  +-------------------+
+//!                                  |                   |
+//!                                  |     ISB Reader    |
+//!                                  |                   |
+//!                                  +--------+----------+
+//!                                           |
+//!                                           | Messages
+//!                                           v
+//! +------------------+            +---------+---------+
+//! |                  |  Replays   |                   |
+//! |       WAL        +----------->|    Persistent     |
+//! | (Write-Ahead Log)|            |       Buffer      |    
+//! |                  |<-----------+       Queue       |  
+//! +------------------+  Persists  |                   |  
+//!                                 |       (PBQ)       |
+//!                                 +---------+---------+
+//!                                           |
+//!                                           | ReceiverStream<Message>
+//!                                           v
+//!                                 +---------+---------+
+//!                                 |                   |
+//!                                 |      Reducer      |
+//!                                 |                   |
+//!                                 +---+-----+-----+---+
+//!                                     |     |     |
+//!                                     |     |     | Window Multiplexer
+//!                                     |     |     |
+//!                                     v     v     v
+//!                           +---------+ +---------+ +---------+
+//!                           |         | |         | |         |
+//!                           | Reduce  | | Reduce  | | Reduce  |
+//!                           | Task 1  | | Task 2  | | Task N  |
+//!                           |         | |         | |         |
+//!                           +---------+ +---------+ +---------+
+//!                                |          |           |
+//!                                |          |           |
+//!                                v          v           v
+//!                           +---------------------------------+
+//!                           |                                 |
+//!                           |          ISB Writer             |
+//!                           |                                 |
+//!                           +---------------------------------+
+//! ```
+//!
+//! ## Error Handling
+//! Errors are propagated from the reduce tasks to the AlignedReduceActor through the error_tx channel.
+//! AlignedReduceActor will cancel the token to signal the upstream to stop sending new messages and
+//! exit with error. This is possible because the streaming reader is awaiting on the cancellation token
+//! (read) while the write/cancel of the cancellation token is done by the Reduce Actor.
+//!
+
+/// Aligned Reduce for Fixed and Sliding Windows.
+pub(crate) mod aligned;
+
+/// User-defined Reduce for Aligned and Unaligned Windows.
+pub(crate) mod user_defined;
diff --git a/rust/numaflow-core/src/reduce/reducer/aligned.rs b/rust/numaflow-core/src/reduce/reducer/aligned.rs
new file mode 100644
index 00000000..617afbd3
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/aligned.rs
@@ -0,0 +1,20 @@
+//! Aligned windows in stream processing are time-based windowing strategies where window boundaries
+//! are synchronized with a global clock (e.g., system time), ensuring consistent window intervals
+//! across all data streams. This alignment simplifies aggregation and analysis by providing uniform
+//! time slices for grouping events.
+//! There are two main types of aligned windows:
+//!   * [Fixed](windower::fixed) (tumbling)
+//!   * [Sliding](windower::sliding).
+//! Fixed windows divide the timeline into non-overlapping intervals of equal length,
+//! such as every 10 seconds, with each event assigned to exactly one window. In contrast, sliding
+//! windows also have a fixed duration but move forward by a smaller slide interval, resulting in
+//! overlapping windows. This allows each event to potentially be part of multiple windows, enabling
+//! more fine-grained, continuous analysis. Both types ensure deterministic and predictable windowing
+//! based on time.
+
+/// Aligned Reduce for Fixed and Sliding Windows.
+pub(crate) mod reducer;
+/// User-defined Reduce for Aligned and Unaligned Windows.
+pub(crate) mod user_defined;
+/// Windower for Aligned Windows.
+pub(crate) mod windower;
diff --git a/rust/numaflow-core/src/reduce/reducer/aligned/reducer.rs b/rust/numaflow-core/src/reduce/reducer/aligned/reducer.rs
new file mode 100644
index 00000000..3ba76dff
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/aligned/reducer.rs
@@ -0,0 +1,1326 @@
+use crate::error::Error;
+use crate::message::Message;
+use crate::pipeline::isb::jetstream::writer::JetstreamWriter;
+use crate::reduce::reducer::aligned::user_defined::UserDefinedAlignedReduce;
+use crate::reduce::reducer::aligned::windower::{
+    AlignedWindowMessage, Window, WindowManager, WindowOperation,
+};
+use crate::reduce::wal::segment::append::{AppendOnlyWal, SegmentWriteMessage};
+use bytes::Bytes;
+use chrono::{DateTime, Utc};
+use numaflow_pb::objects::wal::GcEvent;
+use std::collections::HashMap;
+use std::ops::Sub;
+use std::time::Duration;
+use tokio::sync::mpsc;
+use tokio::task::JoinHandle;
+use tokio_stream::StreamExt;
+use tokio_stream::wrappers::ReceiverStream;
+use tokio_util::sync::CancellationToken;
+use tracing::{debug, error, info};
+
+/// Represents an active reduce stream for a window.
+struct ActiveStream {
+    /// Sender for window messages. Messages are sent to this channel is received by the unique reduce
+    /// task for that window.
+    message_tx: mpsc::Sender<AlignedWindowMessage>,
+    /// Handle to the task processing the window
+    task_handle: JoinHandle<()>,
+}
+
+/// Represents a reduce task for a window. It is responsible for calling the user-defined reduce
+/// function for the given window and writing the output to JetStream and publishing the watermark.
+/// Also writes the GC events to the WAL if configured.
+struct ReduceTask {
+    client: UserDefinedAlignedReduce,
+    js_writer: JetstreamWriter,
+    gc_wal_tx: Option<mpsc::Sender<SegmentWriteMessage>>,
+    error_tx: mpsc::Sender<Error>,
+    window: Window,
+    window_manager: WindowManager,
+}
+
+impl ReduceTask {
+    /// Creates a new ReduceTask with the given configuration
+    fn new(
+        client: UserDefinedAlignedReduce,
+        js_writer: JetstreamWriter,
+        gc_wal_tx: Option<mpsc::Sender<SegmentWriteMessage>>,
+        error_tx: mpsc::Sender<Error>,
+        window: Window,
+        window_manager: WindowManager,
+    ) -> Self {
+        Self {
+            client,
+            js_writer,
+            gc_wal_tx,
+            error_tx,
+            window,
+            window_manager,
+        }
+    }
+
+    /// starts a task to process the window stream and returns the task handle
+    async fn start(
+        mut self,
+        message_stream: ReceiverStream<AlignedWindowMessage>,
+        cln_token: CancellationToken,
+    ) -> JoinHandle<()> {
+        tokio::spawn(async move {
+            let (result_tx, result_rx) = mpsc::channel(100);
+            let result_stream = ReceiverStream::new(result_rx);
+
+            // Spawn a task to write results to JetStream
+            let writer_handle = match self
+                .js_writer
+                .streaming_write(result_stream, cln_token.clone())
+                .await
+            {
+                Ok(handle) => handle,
+                Err(e) => {
+                    error!(?e, "Failed to start JetStream writer");
+                    return;
+                }
+            };
+
+            // Call the reduce function. This is a blocking call and will return only once the window
+            // is closed, cancellation is detected, or on error. The output is sent to the result_tx
+            // channel which is consumed by the writer task and published to JetStream.
+            let result = self
+                .client
+                .reduce_fn(message_stream, result_tx, cln_token)
+                .await;
+
+            if let Err(e) = result {
+                // Check if this is a cancellation error
+                if let Error::Cancelled() = &e {
+                    info!("Cancellation detected while doing reduce operation");
+                    return;
+                }
+
+                // For other errors, log and send to error channel to signal the reduce actor to stop
+                // consuming new messages and exit with error.
+                error!(?e, "Error while doing reduce operation");
+                let _ = self.error_tx.send(e).await;
+                return;
+            }
+
+            // Wait for the writer to complete, write takes care of publishing the watermark.
+            if let Err(e) = writer_handle.await.expect("join failed for js writer task") {
+                error!(?e, "Error while writing results to JetStream");
+                let _ = self.error_tx.send(e).await;
+                return;
+            }
+
+            // oldest window is used to determine the GC event incase of sliding windows, unlike fixed
+            // messages can be part of multiple windows in sliding, so we can only gc the messages
+            // that are less than the oldest window's start time.
+            let oldest_window = self
+                .window_manager
+                .oldest_window()
+                .expect("no oldest window found");
+
+            // we can safely delete the window from the window manager since the results are
+            // successfully written to jetstream and watermark is published.
+            self.window_manager.delete_window(self.window.clone());
+
+            // now that the processing is done, we can add this window to the GC WAL.
+            let Some(gc_wal_tx) = &self.gc_wal_tx else {
+                // return if the GC WAL is not configured
+                return;
+            };
+
+            // Send GC event if WAL is configured
+            let gc_event: GcEvent = if let WindowManager::Sliding(_) = self.window_manager {
+                // for sliding window a message can be part of multiple windows, we can only delete the
+                // messages that are less than the oldest window's start time.
+                Window {
+                    start_time: oldest_window.start_time,
+                    end_time: oldest_window.start_time,
+                    id: oldest_window.id,
+                }
+                .into()
+            } else {
+                // messages of fixed window are not part of multiple windows, so we can delete all the
+                // messages that are less than the window's end time.
+                self.window.clone().into()
+            };
+
+            debug!(?gc_event, "Sending GC event to WAL");
+            gc_wal_tx
+                .send(SegmentWriteMessage::WriteData {
+                    offset: None,
+                    data: prost::Message::encode_to_vec(&gc_event).into(),
+                })
+                .await
+                .expect("failed to write gc event to wal");
+        })
+    }
+}
+
+/// Actor that manages multiple window reduction streams. It manages [ActiveStream]s and manages the
+/// lifecycle of the reduce tasks.
+struct AlignedReduceActor {
+    /// It multiplexes the messages to the receiver to the reduce tasks through the corresponding
+    /// tx in [ActiveStream].
+    receiver: mpsc::Receiver<AlignedWindowMessage>,
+    /// Client for user-defined reduce operations.
+    client: UserDefinedAlignedReduce,
+    /// Map of [ActiveStream]s keyed by window ID (pnf_slot).
+    active_streams: HashMap<Bytes, ActiveStream>,
+    /// JetStream writer for writing results of reduce operation.
+    js_writer: JetstreamWriter,
+    /// Sender for error messages.
+    error_tx: mpsc::Sender<Error>,
+    /// Sender for GC WAL messages. It is optional since users can specify not to use WAL.
+    gc_wal_tx: Option<mpsc::Sender<SegmentWriteMessage>>,
+    /// WindowManager for assigning windows to messages and closing windows.
+    window_manager: WindowManager,
+    /// Cancellation token to signal tasks to stop
+    cln_token: CancellationToken,
+}
+
+impl AlignedReduceActor {
+    /// Waits for all active tasks to complete
+    async fn wait_for_all_tasks(&mut self) {
+        info!(
+            "Waiting for {} active reduce tasks to complete",
+            self.active_streams.len()
+        );
+
+        // Collect all active streams to avoid borrowing issues
+        let active_streams: Vec<_> = self.active_streams.drain().collect();
+
+        for (window_id, active_stream) in active_streams {
+            // Wait for the task to complete
+            if let Err(e) = active_stream.task_handle.await {
+                error!(?window_id, err = ?e, "Reduce task for window failed during shutdown");
+            }
+            info!(?window_id, "Reduce task for window completed");
+        }
+
+        info!("All reduce tasks completed");
+    }
+
+    pub(crate) async fn new(
+        client: UserDefinedAlignedReduce,
+        receiver: mpsc::Receiver<AlignedWindowMessage>,
+        js_writer: JetstreamWriter,
+        error_tx: mpsc::Sender<Error>,
+        gc_wal_tx: Option<mpsc::Sender<SegmentWriteMessage>>,
+        window_manager: WindowManager,
+        cln_token: CancellationToken,
+    ) -> Self {
+        Self {
+            client,
+            receiver,
+            active_streams: HashMap::new(),
+            js_writer,
+            error_tx,
+            gc_wal_tx,
+            window_manager,
+            cln_token,
+        }
+    }
+
+    /// Runs the actor, listening for messages and multiplexing them to the reduce tasks.
+    async fn run(mut self) {
+        while let Some(msg) = self.receiver.recv().await {
+            self.handle_window_message(msg).await;
+        }
+        self.wait_for_all_tasks().await;
+    }
+
+    /// Handle a window message based on its operation type
+    async fn handle_window_message(&mut self, window_msg: AlignedWindowMessage) {
+        match &window_msg.operation {
+            WindowOperation::Open(_) => self.window_open(window_msg).await,
+            WindowOperation::Append(_) => self.window_append(window_msg).await,
+            WindowOperation::Close => self.window_close(window_msg).await,
+        }
+    }
+
+    /// Creates a new reduce task for the window and sends the initial Open command with the
+    /// first message.
+    async fn window_open(&mut self, window_msg: AlignedWindowMessage) {
+        // Create a new channel for this window's messages
+        let (message_tx, message_rx) = mpsc::channel(100);
+        let message_stream = ReceiverStream::new(message_rx);
+        let window = window_msg.window.clone();
+
+        // Create a ReduceTask
+        let reduce_task = ReduceTask::new(
+            self.client.clone(),
+            self.js_writer.clone(),
+            self.gc_wal_tx.clone(),
+            self.error_tx.clone(),
+            window.clone(),
+            self.window_manager.clone(),
+        );
+
+        // start the reduce task and store the handle and the sender so that we can send messages
+        // and wait for it to complete.
+        let task_handle = reduce_task
+            .start(message_stream, self.cln_token.clone())
+            .await;
+
+        self.active_streams.insert(
+            window.pnf_slot(),
+            ActiveStream {
+                message_tx: message_tx.clone(),
+                task_handle,
+            },
+        );
+
+        // Send the open command with the first message
+        message_tx.send(window_msg).await.expect("Receiver dropped");
+    }
+
+    /// sends the message to the reduce task for the window.
+    async fn window_append(&mut self, window_msg: AlignedWindowMessage) {
+        let window = window_msg.window.clone();
+        let window_id = window.pnf_slot();
+
+        // Get the existing stream or log error if not found create a new one.
+        let Some(active_stream) = self.active_streams.get(&window_id) else {
+            // windows may not be found during replay, because the windower doesn't send the open
+            // message for the active windows that got replayed, hence we create a new one.
+            // this happens because of out-of-order messages and we have to ensure that the (t+1)th
+            // message is sent to the window that could be created by (t)th message iff (t+1)th message
+            // belongs to that window created by (t)th message.
+            self.window_open(window_msg).await;
+            return;
+        };
+
+        // Send the append message
+        active_stream
+            .message_tx
+            .send(window_msg)
+            .await
+            .expect("Receiver dropped");
+    }
+
+    /// Closes the reduce task for the window.
+    async fn window_close(&mut self, window_msg: AlignedWindowMessage) {
+        let window_id = window_msg.window.pnf_slot();
+
+        // Get the existing stream or log error if not found
+        let Some(active_stream) = self.active_streams.remove(&window_id) else {
+            error!("No active stream found for window {:?}", window_id);
+            return;
+        };
+
+        // we don't need to write the close message to the client, stream closing
+        // is considered as close for aligned windows.
+        // Drop the sender to signal completion
+        drop(active_stream.message_tx);
+
+        // Wait for the task to complete
+        if let Err(e) = active_stream.task_handle.await {
+            error!(?window_id, err = ?e,"Reduce task for window failed");
+        }
+    }
+}
+
+/// Processes messages and forwards results to the next stage.
+pub(crate) struct AlignedReducer {
+    client: UserDefinedAlignedReduce,
+    /// Window manager for assigning windows to messages and closing windows.
+    window_manager: WindowManager,
+    /// Writer for writing results to JetStream
+    js_writer: JetstreamWriter,
+    /// Final state of the component (any error will set this as Err).
+    final_result: crate::Result<()>,
+    /// Set to true when shutting down due to an error.
+    shutting_down_on_err: bool,
+    /// WAL for writing GC events
+    gc_wal: Option<AppendOnlyWal>,
+    /// Allowed lateness for the messages to be accepted and delay the close of book.
+    allowed_lateness: Duration,
+    /// current watermark for the reduce vertex.
+    current_watermark: DateTime<Utc>,
+}
+
+impl AlignedReducer {
+    pub(crate) async fn new(
+        client: UserDefinedAlignedReduce,
+        window_manager: WindowManager,
+        js_writer: JetstreamWriter,
+        gc_wal: Option<AppendOnlyWal>,
+        allowed_lateness: Duration,
+    ) -> Self {
+        Self {
+            client,
+            window_manager,
+            js_writer,
+            final_result: Ok(()),
+            shutting_down_on_err: false,
+            gc_wal,
+            allowed_lateness,
+            current_watermark: DateTime::from_timestamp_millis(-1).expect("Invalid timestamp"),
+        }
+    }
+
+    /// Starts the reduce component and returns a handle to the main task.
+    pub(crate) async fn start(
+        mut self,
+        input_stream: ReceiverStream<Message>,
+        cln_token: CancellationToken,
+    ) -> crate::Result<JoinHandle<crate::Result<()>>> {
+        // Set up error and GC channels
+        let (error_tx, mut error_rx) = mpsc::channel(10);
+        let gc_wal_handle = self.setup_gc_wal().await?;
+
+        // Create the actor channel and start the actor
+        let (actor_tx, actor_rx) = mpsc::channel(100);
+        let actor = AlignedReduceActor::new(
+            self.client.clone(),
+            actor_rx,
+            self.js_writer.clone(),
+            error_tx.clone(),
+            gc_wal_handle,
+            self.window_manager.clone(),
+            cln_token.clone(),
+        )
+        .await;
+
+        // start the reduce actor
+        let actor_handle = tokio::spawn(async move {
+            actor.run().await;
+        });
+
+        // Start the main task
+        let handle = tokio::spawn(async move {
+            let mut input_stream = input_stream;
+
+            loop {
+                tokio::select! {
+                    // Check for errors from reduce tasks
+                    Some(err) = error_rx.recv() => {
+                        self.handle_error(err, &cln_token);
+                    }
+
+                    // Process input messages
+                    read_msg = input_stream.next() => {
+                        let Some(msg) = read_msg else {
+                            break;
+                        };
+
+                        // update the watermark.
+                        // we cannot simply assign incoming message's watermark as the current watermark,
+                        // because it can be -1. watermark will never regress, so use max.
+                        self.current_watermark = self.current_watermark.max(msg.watermark.unwrap_or_default());
+
+                        // only drop the message if it is late and the event time is before the watermark - allowed lateness
+                        if msg.is_late && msg.event_time < self.current_watermark.sub(self.allowed_lateness) {
+                            // TODO(ajain): add a metric for this
+                            continue;
+                        }
+
+                        if self.current_watermark > msg.event_time {
+                            error!(current_watermark=?self.current_watermark, message_event_time=?msg.event_time, "Old message popped up, Watermark is behind the event time");
+                            continue;
+                        }
+
+                        // If shutting down, drain the stream
+                        if self.shutting_down_on_err {
+                            info!("Reduce component is shutting down due to an error, not accepting the message");
+                            continue;
+                        }
+
+                        // check if any windows can be closed
+                        let window_messages = self.window_manager.close_windows(self.current_watermark.sub(self.allowed_lateness));
+                        for window_msg in window_messages {
+                            actor_tx.send(window_msg).await.expect("Receiver dropped");
+                        }
+
+                        // assign windows to the message
+                        let window_messages = self.window_manager.assign_windows(msg);
+
+                        // Send each window message to the actor for processing
+                        for window_msg in window_messages {
+                            actor_tx.send(window_msg).await.expect("Receiver dropped");
+                        }
+                    }
+                }
+            }
+
+            // Drop the sender to signal the actor to stop
+            info!("Reduce component is shutting down, waiting for active reduce tasks to complete");
+            drop(actor_tx);
+
+            // Wait for the actor to complete
+            if let Err(e) = actor_handle.await {
+                error!("Error waiting for actor to complete: {:?}", e);
+            }
+
+            // For sliding: we need to make sure to store the window manager state before exiting
+            // from the reducer component
+            if let WindowManager::Sliding(manager) = self.window_manager {
+                if let Err(e) = manager.save_state() {
+                    error!("Failed to save window state: {:?}", e);
+                }
+            }
+
+            info!(status=?self.final_result, "Reduce component successfully completed");
+            self.final_result
+        });
+
+        Ok(handle)
+    }
+
+    /// Set up the GC WAL if configured.
+    async fn setup_gc_wal(&mut self) -> crate::Result<Option<mpsc::Sender<SegmentWriteMessage>>> {
+        if let Some(gc_wal) = self.gc_wal.take() {
+            let (gc_tx, gc_rx) = mpsc::channel(100);
+            gc_wal.streaming_write(ReceiverStream::new(gc_rx)).await?;
+            Ok(Some(gc_tx))
+        } else {
+            Ok(None)
+        }
+    }
+
+    /// Handles errors from reduce tasks, cancels the token to signal the upstream to stop sending
+    /// new messages and updates the final result and shutting_down_on_err flags so that we can
+    /// go to shut down mode by draining the input stream and exit.
+    fn handle_error(&mut self, error: Error, cln_token: &CancellationToken) {
+        if self.final_result.is_ok() {
+            error!(?error, "Error received while performing reduce operation");
+            cln_token.cancel();
+            self.final_result = Err(error);
+            self.shutting_down_on_err = true;
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::collections::HashSet;
+    use std::sync::Arc;
+    use std::time::Duration;
+
+    use super::*;
+    use crate::config::pipeline::ToVertexConfig;
+    use crate::config::pipeline::isb::{BufferWriterConfig, Stream};
+    use crate::message::{Message, MessageID, Offset, StringOffset};
+    use crate::pipeline::isb::jetstream::writer::JetstreamWriter;
+    use crate::reduce::reducer::aligned::user_defined::UserDefinedAlignedReduce;
+    use crate::reduce::reducer::aligned::windower::fixed::FixedWindowManager;
+    use crate::reduce::reducer::aligned::windower::sliding::SlidingWindowManager;
+    use crate::shared::grpc::create_rpc_channel;
+    use crate::tracker::TrackerHandle;
+    use async_nats::jetstream::consumer::PullConsumer;
+    use async_nats::jetstream::{self, consumer, stream};
+    use chrono::{TimeZone, Utc};
+    use numaflow::reduce;
+    use numaflow_pb::clients::reduce::reduce_client::ReduceClient;
+    use prost::Message as ProstMessage;
+    use tempfile::TempDir;
+    use tokio::sync::mpsc;
+    use tokio_stream::wrappers::ReceiverStream;
+    use tokio_util::sync::CancellationToken;
+
+    struct Counter {}
+
+    struct CounterCreator {}
+
+    impl reduce::ReducerCreator for CounterCreator {
+        type R = Counter;
+
+        fn create(&self) -> Self::R {
+            Counter::new()
+        }
+    }
+
+    impl Counter {
+        fn new() -> Self {
+            Self {}
+        }
+    }
+
+    #[tonic::async_trait]
+    impl reduce::Reducer for Counter {
+        async fn reduce(
+            &self,
+            keys: Vec<String>,
+            mut input: mpsc::Receiver<reduce::ReduceRequest>,
+            _md: &reduce::Metadata,
+        ) -> Vec<reduce::Message> {
+            let mut counter = 0;
+            // the loop exits when input is closed which will happen only on close of book.
+            while input.recv().await.is_some() {
+                counter += 1;
+            }
+            vec![reduce::Message::new(counter.to_string().into_bytes()).with_keys(keys.clone())]
+        }
+    }
+
+    #[cfg(feature = "nats-tests")]
+    #[tokio::test]
+    async fn test_aligned_reducer_with_fixed_window() -> crate::Result<()> {
+        // Set up the reducer server
+        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("reduce_fixed.sock");
+        let server_info_file = tmp_dir.path().join("reduce_fixed-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let server_handle = tokio::spawn(async move {
+            reduce::Server::new(CounterCreator {})
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(shutdown_rx)
+                .await
+                .expect("server failed");
+        });
+
+        // Wait for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        // Create the client
+        let client =
+            UserDefinedAlignedReduce::new(ReduceClient::new(create_rpc_channel(sock_file).await?))
+                .await;
+
+        // Create a fixed window manager with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+
+        // Set up JetStream
+        let js_url = "localhost:4222";
+        let nats_client = async_nats::connect(js_url).await.unwrap();
+        let js_context = jetstream::new(nats_client);
+
+        // Create output stream
+        let stream = Stream::new("test_aligned_reducer_fixed", "test", 0);
+        // Delete stream if it exists
+        let _ = js_context.delete_stream(stream.name).await;
+
+        let _stream = js_context
+            .get_or_create_stream(stream::Config {
+                name: stream.name.to_string(),
+                subjects: vec![stream.name.to_string()],
+                ..Default::default()
+            })
+            .await
+            .unwrap();
+
+        // Create consumer
+        let _consumer = js_context
+            .create_consumer_on_stream(
+                consumer::Config {
+                    name: Some(stream.name.to_string()),
+                    ack_policy: consumer::AckPolicy::Explicit,
+                    ..Default::default()
+                },
+                stream.name,
+            )
+            .await
+            .unwrap();
+
+        // Create JetstreamWriter
+        let cln_token = CancellationToken::new();
+        let tracker_handle = TrackerHandle::new(None, None);
+        let js_writer = JetstreamWriter::new(
+            vec![ToVertexConfig {
+                name: "test-vertex",
+                partitions: 1,
+                writer_config: BufferWriterConfig {
+                    streams: vec![stream.clone()],
+                    ..Default::default()
+                },
+                conditions: None,
+            }],
+            js_context.clone(),
+            100,
+            tracker_handle.clone(),
+            cln_token.clone(),
+            None,
+            "Reduce".to_string(),
+        );
+
+        // Create the AlignedReducer
+        let reducer = AlignedReducer::new(
+            client,
+            WindowManager::Fixed(windower),
+            js_writer,
+            None, // No GC WAL for testing
+            Duration::from_secs(0),
+        )
+        .await;
+
+        // Create a channel for input messages
+        let (input_tx, input_rx) = mpsc::channel(10);
+        let input_stream = ReceiverStream::new(input_rx);
+
+        // Start the reducer
+        let reducer_handle = reducer.start(input_stream, cln_token.clone()).await?;
+
+        // Create test messages
+        let base_time = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
+
+        // Message 1: Within the first window
+        let msg1 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value1".into(),
+            offset: Offset::String(StringOffset::new("0".to_string(), 0)),
+            event_time: base_time + chrono::Duration::seconds(10),
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "0".to_string().into(),
+                index: 0,
+            },
+            ..Default::default()
+        };
+
+        // Message 2: Within the first window
+        let msg2 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value2".into(),
+            offset: Offset::String(StringOffset::new("1".to_string(), 1)),
+            event_time: base_time + chrono::Duration::seconds(30),
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "1".to_string().into(),
+                index: 1,
+            },
+            ..Default::default()
+        };
+
+        // Message 3: Within the first window
+        let msg3 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value3".into(),
+            offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+            event_time: base_time + chrono::Duration::seconds(50),
+            watermark: Some(base_time + chrono::Duration::seconds(40)),
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "2".to_string().into(),
+                index: 2,
+            },
+            ..Default::default()
+        };
+
+        // Message 4: Within the first window but with watermark past window end
+        let msg4 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value3".into(),
+            offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+            event_time: base_time + chrono::Duration::seconds(80),
+            watermark: Some(base_time + chrono::Duration::seconds(70)), // Past window end
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "2".to_string().into(),
+                index: 2,
+            },
+            ..Default::default()
+        };
+
+        // Send the messages
+        input_tx.send(msg1).await.unwrap();
+        input_tx.send(msg2).await.unwrap();
+
+        // Send message with watermark past window end to trigger window close
+        input_tx.send(msg3).await.unwrap();
+        input_tx.send(msg4).await.unwrap();
+
+        // Create a consumer to read the results
+        let consumer: PullConsumer = js_context
+            .get_consumer_from_stream(&stream.name, &stream.name)
+            .await
+            .unwrap();
+
+        // Read messages from the stream
+        let mut messages = consumer
+            .fetch()
+            .expires(Duration::from_secs(1))
+            .messages()
+            .await
+            .unwrap();
+
+        let mut result_count = 0;
+
+        while let Some(msg) = messages.next().await {
+            let msg = msg.unwrap();
+
+            let data = msg.payload.to_vec();
+
+            // Acknowledge the message
+            msg.ack().await.unwrap();
+
+            // Parse the message
+            let message: numaflow_pb::objects::isb::Message =
+                prost::Message::decode(data.as_ref()).unwrap();
+
+            // Verify the result
+            assert_eq!(message.header.unwrap().keys.to_vec(), vec!["key1"]);
+            assert_eq!(message.body.unwrap().payload.to_vec(), b"3".to_vec());
+
+            result_count += 1;
+        }
+
+        assert_eq!(result_count, 1, "Expected exactly one result message");
+
+        cln_token.cancel();
+        drop(input_tx);
+
+        // Wait for the reducer to complete
+        reducer_handle.await.expect("reducer handle failed")?;
+
+        // Shutdown the server
+        shutdown_tx
+            .send(())
+            .expect("failed to send shutdown signal");
+
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        // Wait for the server to shut down
+        assert!(
+            server_handle.is_finished(),
+            "Expected gRPC server to have shut down"
+        );
+
+        // Clean up JetStream
+        js_context.delete_stream(stream.name).await.unwrap();
+
+        Ok(())
+    }
+
+    #[cfg(feature = "nats-tests")]
+    #[tokio::test]
+    async fn test_aligned_reducer_with_sliding_window() -> crate::Result<()> {
+        // Set up the reducer server
+        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("reduce_sliding.sock");
+        let server_info_file = tmp_dir.path().join("reduce_sliding-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let server_handle = tokio::spawn(async move {
+            reduce::Server::new(CounterCreator {})
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(shutdown_rx)
+                .await
+                .expect("server failed");
+        });
+
+        // Wait for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        // Create the client
+        let client =
+            UserDefinedAlignedReduce::new(ReduceClient::new(create_rpc_channel(sock_file).await?))
+                .await;
+
+        // Create a sliding window manager with 60s window length and 20s slide
+        let windower =
+            SlidingWindowManager::new(Duration::from_secs(60), Duration::from_secs(20), None);
+
+        // Set up JetStream
+        let js_url = "localhost:4222";
+        let nats_client = async_nats::connect(js_url).await.unwrap();
+        let js_context = jetstream::new(nats_client);
+
+        // Create output stream
+        let stream = Stream::new("test_aligned_reducer_sliding", "test", 0);
+        // Delete stream if it exists
+        let _ = js_context.delete_stream(stream.name).await;
+        let _stream = js_context
+            .get_or_create_stream(stream::Config {
+                name: stream.name.to_string(),
+                subjects: vec![stream.name.to_string()],
+                ..Default::default()
+            })
+            .await
+            .unwrap();
+
+        // Create consumer
+        let _consumer = js_context
+            .create_consumer_on_stream(
+                consumer::Config {
+                    name: Some(stream.name.to_string()),
+                    ack_policy: consumer::AckPolicy::Explicit,
+                    ..Default::default()
+                },
+                stream.name,
+            )
+            .await
+            .unwrap();
+
+        // Create JetstreamWriter
+        let cln_token = CancellationToken::new();
+        let tracker_handle = TrackerHandle::new(None, None);
+        let js_writer = JetstreamWriter::new(
+            vec![ToVertexConfig {
+                name: "test-vertex",
+                partitions: 1,
+                writer_config: BufferWriterConfig {
+                    streams: vec![stream.clone()],
+                    ..Default::default()
+                },
+                conditions: None,
+            }],
+            js_context.clone(),
+            100,
+            tracker_handle.clone(),
+            cln_token.clone(),
+            None,
+            "Reduce".to_string(),
+        );
+
+        // Create the AlignedReducer
+        let reducer = AlignedReducer::new(
+            client,
+            WindowManager::Sliding(windower),
+            js_writer,
+            None, // No GC WAL for testing
+            Duration::from_secs(0),
+        )
+        .await;
+
+        // Create a channel for input messages
+        let (input_tx, input_rx) = mpsc::channel(100);
+        let input_stream = ReceiverStream::new(input_rx);
+
+        // Start the reducer
+        let reducer_handle = reducer.start(input_stream, cln_token.clone()).await?;
+
+        // Create test messages
+        let base_time = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
+
+        // Message 1: Within the first set of sliding windows
+        let msg1 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value1".into(),
+            offset: Offset::String(StringOffset::new("0".to_string(), 0)),
+            event_time: base_time,
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "0".to_string().into(),
+                index: 0,
+            },
+            ..Default::default()
+        };
+
+        // Message 2: Within the first set of sliding windows
+        let msg2 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value2".into(),
+            offset: Offset::String(StringOffset::new("1".to_string(), 1)),
+            event_time: base_time,
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "1".to_string().into(),
+                index: 1,
+            },
+            ..Default::default()
+        };
+
+        // Message 3: Within the first window
+        let msg3 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value3".into(),
+            offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+            event_time: base_time,
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "2".to_string().into(),
+                index: 2,
+            },
+            ..Default::default()
+        };
+
+        // Message 4: Within the first window but with watermark past window end
+        let msg4 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value3".into(),
+            offset: Offset::String(StringOffset::new("3".to_string(), 2)),
+            event_time: base_time + chrono::Duration::seconds(120),
+            watermark: Some(base_time + chrono::Duration::seconds(100)), // Past window end
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "3".to_string().into(),
+                index: 3,
+            },
+            ..Default::default()
+        };
+
+        // Send the messages
+        input_tx.send(msg1).await.unwrap();
+        input_tx.send(msg2).await.unwrap();
+        input_tx.send(msg3).await.unwrap();
+        // Send message with watermark past window end to trigger window close
+        input_tx.send(msg4).await.unwrap();
+
+        // Create a consumer to read the results
+        let consumer: PullConsumer = js_context
+            .get_consumer_from_stream(&stream.name, &stream.name)
+            .await
+            .unwrap();
+
+        // Read messages from the stream
+        let mut messages = consumer
+            .batch()
+            .expires(Duration::from_secs(1))
+            .max_messages(3)
+            .messages()
+            .await
+            .unwrap();
+
+        let mut result_count = 0;
+
+        while let Some(msg) = messages.next().await {
+            let msg = msg.unwrap();
+            let data = msg.payload.to_vec();
+
+            // Acknowledge the message
+            msg.ack().await.unwrap();
+
+            // Parse the message
+            let proto_message = numaflow_pb::objects::isb::Message::decode(&data[..]).unwrap();
+
+            // Verify the result
+            assert_eq!(proto_message.header.unwrap().keys.to_vec(), vec!["key1"]);
+            assert_eq!(
+                String::from_utf8(proto_message.body.unwrap().payload.to_vec()).unwrap(),
+                "3"
+            ); // Counter should be 3
+
+            result_count += 1;
+        }
+
+        assert_eq!(
+            result_count, 3,
+            "Expected exactly three result messages for sliding windows"
+        );
+
+        cln_token.cancel();
+        drop(input_tx);
+
+        // Wait for the reducer to complete
+        reducer_handle.await.expect("reducer handle failed")?;
+
+        // Shutdown the server
+        shutdown_tx
+            .send(())
+            .expect("failed to send shutdown signal");
+
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        // Wait for the server to shut down
+        assert!(
+            server_handle.is_finished(),
+            "Expected gRPC server to have shut down"
+        );
+
+        // Clean up JetStream
+        js_context.delete_stream(stream.name).await.unwrap();
+
+        Ok(())
+    }
+
+    #[cfg(feature = "nats-tests")]
+    #[tokio::test]
+    async fn test_aligned_reducer_with_multiple_keys() -> crate::Result<()> {
+        // Set up the reducer server
+        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("reduce_multi_keys.sock");
+        let server_info_file = tmp_dir.path().join("reduce_multi_keys-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let server_handle = tokio::spawn(async move {
+            reduce::Server::new(CounterCreator {})
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(shutdown_rx)
+                .await
+                .expect("server failed");
+        });
+
+        // Wait for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        // Create the client
+        let client =
+            UserDefinedAlignedReduce::new(ReduceClient::new(create_rpc_channel(sock_file).await?))
+                .await;
+
+        // Create a fixed window manager with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+
+        // Set up JetStream
+        let js_url = "localhost:4222";
+        let nats_client = async_nats::connect(js_url).await.unwrap();
+        let js_context = jetstream::new(nats_client);
+
+        // Create output stream
+        let stream = Stream::new("test_aligned_reducer_multi_keys", "test", 0);
+        // Delete stream if it exists
+        let _ = js_context.delete_stream(stream.name).await;
+        let _stream = js_context
+            .get_or_create_stream(stream::Config {
+                name: stream.name.to_string(),
+                subjects: vec![stream.name.to_string()],
+                ..Default::default()
+            })
+            .await
+            .unwrap();
+
+        // Create consumer
+        let _consumer = js_context
+            .create_consumer_on_stream(
+                consumer::Config {
+                    name: Some(stream.name.to_string()),
+                    ack_policy: consumer::AckPolicy::Explicit,
+                    ..Default::default()
+                },
+                stream.name,
+            )
+            .await
+            .unwrap();
+
+        // Create JetstreamWriter
+        let cln_token = CancellationToken::new();
+        let tracker_handle = TrackerHandle::new(None, None);
+        let js_writer = JetstreamWriter::new(
+            vec![ToVertexConfig {
+                name: "test-vertex",
+                partitions: 1,
+                writer_config: BufferWriterConfig {
+                    streams: vec![stream.clone()],
+                    ..Default::default()
+                },
+                conditions: None,
+            }],
+            js_context.clone(),
+            100,
+            tracker_handle.clone(),
+            cln_token.clone(),
+            None,
+            "Reduce".to_string(),
+        );
+
+        // Create the AlignedReducer
+        let reducer = AlignedReducer::new(
+            client,
+            WindowManager::Fixed(windower),
+            js_writer,
+            None, // No GC WAL for testing
+            Duration::from_secs(0),
+        )
+        .await;
+
+        // Create a channel for input messages
+        let (input_tx, input_rx) = mpsc::channel(100);
+        let input_stream = ReceiverStream::new(input_rx);
+
+        // Start the reducer
+        let reducer_handle = reducer.start(input_stream, cln_token.clone()).await?;
+
+        // Create test messages
+        let base_time = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
+
+        // Message 1: Within the first window for key1
+        let msg1 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value1".into(),
+            offset: Offset::String(StringOffset::new("0".to_string(), 0)),
+            event_time: base_time + chrono::Duration::seconds(10),
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "0".to_string().into(),
+                index: 0,
+            },
+            ..Default::default()
+        };
+
+        // Message 2: Within the first window for key2
+        let msg2 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key2".into()]),
+            tags: None,
+            value: "value2".into(),
+            offset: Offset::String(StringOffset::new("1".to_string(), 1)),
+            event_time: base_time + chrono::Duration::seconds(20),
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "1".to_string().into(),
+                index: 1,
+            },
+            ..Default::default()
+        };
+
+        // Message 3: Within the first window for key1
+        let msg3 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value3".into(),
+            offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+            event_time: base_time + chrono::Duration::seconds(30),
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "2".to_string().into(),
+                index: 2,
+            },
+            ..Default::default()
+        };
+
+        // Message 4: Within the first window for key2
+        let msg4 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key2".into()]),
+            tags: None,
+            value: "value4".into(),
+            offset: Offset::String(StringOffset::new("3".to_string(), 3)),
+            event_time: base_time + chrono::Duration::seconds(40),
+            watermark: None,
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "3".to_string().into(),
+                index: 3,
+            },
+            ..Default::default()
+        };
+
+        // Message 5: With watermark past the first window end for key1
+        let msg5 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key1".into()]),
+            tags: None,
+            value: "value5".into(),
+            offset: Offset::String(StringOffset::new("4".to_string(), 4)),
+            event_time: base_time + chrono::Duration::seconds(90),
+            watermark: Some(base_time + chrono::Duration::seconds(70)), // Past window end
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "4".to_string().into(),
+                index: 4,
+            },
+            ..Default::default()
+        };
+
+        // Message 6: With watermark past the first window end for key2
+        let msg6 = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["key2".into()]),
+            tags: None,
+            value: "value6".into(),
+            offset: Offset::String(StringOffset::new("5".to_string(), 5)),
+            event_time: base_time + chrono::Duration::seconds(90),
+            watermark: Some(base_time + chrono::Duration::seconds(80)), // Past window end
+            id: MessageID {
+                vertex_name: "vertex_name".to_string().into(),
+                offset: "5".to_string().into(),
+                index: 5,
+            },
+            ..Default::default()
+        };
+
+        // Send the messages
+        input_tx.send(msg1).await.unwrap();
+        input_tx.send(msg2).await.unwrap();
+        input_tx.send(msg3).await.unwrap();
+        input_tx.send(msg4).await.unwrap();
+        input_tx.send(msg5).await.unwrap();
+        input_tx.send(msg6).await.unwrap();
+
+        // Create a consumer to read the results
+        let consumer: PullConsumer = js_context
+            .get_consumer_from_stream(&stream.name, &stream.name)
+            .await
+            .unwrap();
+
+        // Read messages from the stream
+        // Read messages from the stream
+        let mut messages = consumer
+            .batch()
+            .expires(Duration::from_secs(1))
+            .max_messages(2)
+            .messages()
+            .await
+            .unwrap();
+
+        let mut result_count = 0;
+        let mut received_keys = HashSet::new();
+
+        while let Some(msg) = messages.next().await {
+            let msg = msg.unwrap();
+            let data = msg.payload.to_vec();
+
+            // Acknowledge the message
+            msg.ack().await.unwrap();
+
+            // Parse the message
+            let proto_message = numaflow_pb::objects::isb::Message::decode(&data[..]).unwrap();
+
+            // Extract and store the key
+            let key = proto_message.header.unwrap().keys[0].clone();
+            received_keys.insert(key);
+
+            result_count += 1;
+        }
+
+        // Verify we received results for both keys
+        assert!(received_keys.contains("key1"), "Missing result for key1");
+        assert!(received_keys.contains("key2"), "Missing result for key2");
+        assert_eq!(
+            result_count, 2,
+            "Expected exactly two result messages for two keys"
+        );
+
+        cln_token.cancel();
+        drop(input_tx);
+
+        // Wait for the reducer to complete
+        reducer_handle.await.expect("reducer handle failed")?;
+
+        // Shutdown the server
+        shutdown_tx
+            .send(())
+            .expect("failed to send shutdown signal");
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        // Wait for the server to shut down
+        assert!(
+            server_handle.is_finished(),
+            "Expected gRPC server to have shut down"
+        );
+
+        // Clean up JetStream
+        js_context.delete_stream(stream.name).await.unwrap();
+
+        Ok(())
+    }
+}
diff --git a/rust/numaflow-core/src/reduce/reducer/aligned/user_defined.rs b/rust/numaflow-core/src/reduce/reducer/aligned/user_defined.rs
new file mode 100644
index 00000000..d08823f1
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/aligned/user_defined.rs
@@ -0,0 +1,811 @@
+use crate::Result;
+use crate::config::get_vertex_name;
+use crate::message::{IntOffset, Message, MessageID, Offset};
+use crate::reduce::reducer::aligned::windower::AlignedWindowMessage;
+use crate::reduce::reducer::aligned::windower::WindowOperation;
+use crate::shared::grpc::{prost_timestamp_from_utc, utc_from_timestamp};
+use numaflow_pb::clients::reduce::reduce_client::ReduceClient;
+use numaflow_pb::clients::reduce::{ReduceRequest, ReduceResponse, reduce_request};
+use std::sync::Arc;
+use tokio_stream::StreamExt;
+use tokio_stream::wrappers::ReceiverStream;
+use tokio_util::sync::CancellationToken;
+use tonic::transport::Channel;
+use tracing::info;
+
+impl From<Message> for reduce_request::Payload {
+    fn from(msg: Message) -> Self {
+        Self {
+            keys: msg.keys.to_vec(),
+            value: msg.value.to_vec(),
+            event_time: Some(prost_timestamp_from_utc(msg.event_time)),
+            watermark: msg.watermark.map(prost_timestamp_from_utc),
+            headers: msg.headers.clone(),
+        }
+    }
+}
+
+impl From<&Message> for reduce_request::Payload {
+    fn from(msg: &Message) -> Self {
+        Self {
+            keys: msg.keys.to_vec(),
+            value: msg.value.to_vec(),
+            event_time: Some(prost_timestamp_from_utc(msg.event_time)),
+            watermark: msg.watermark.map(prost_timestamp_from_utc),
+            headers: msg.headers.clone(),
+        }
+    }
+}
+
+impl From<AlignedWindowMessage> for ReduceRequest {
+    fn from(value: AlignedWindowMessage) -> Self {
+        let window = &value.window;
+        let operation = &value.operation;
+
+        let window_obj = numaflow_pb::clients::reduce::Window {
+            start: Some(prost_timestamp_from_utc(window.start_time)),
+            end: Some(prost_timestamp_from_utc(window.end_time)),
+            slot: "0".to_string(),
+        };
+
+        // Process the operation
+        match operation {
+            WindowOperation::Open(msg) => {
+                let operation = Some(reduce_request::WindowOperation {
+                    event: reduce_request::window_operation::Event::Open as i32,
+                    windows: vec![window_obj],
+                });
+
+                ReduceRequest {
+                    payload: Some(msg.into()),
+                    operation,
+                }
+            }
+            WindowOperation::Close => {
+                let operation = Some(reduce_request::WindowOperation {
+                    event: reduce_request::window_operation::Event::Close as i32,
+                    windows: vec![window_obj],
+                });
+
+                // For Close operations, we still need to set payload to Some
+                // even though there's no message, to ensure consistent behavior
+                ReduceRequest {
+                    payload: Some(reduce_request::Payload {
+                        keys: vec![],
+                        value: vec![],
+                        event_time: None,
+                        watermark: None,
+                        headers: Default::default(),
+                    }),
+                    operation,
+                }
+            }
+            WindowOperation::Append(msg) => {
+                let operation = Some(reduce_request::WindowOperation {
+                    event: reduce_request::window_operation::Event::Append as i32,
+                    windows: vec![window_obj],
+                });
+
+                ReduceRequest {
+                    payload: Some(msg.into()),
+                    operation,
+                }
+            }
+        }
+    }
+}
+
+/// Wrapper for ReduceResponse that includes index and vertex name.
+struct UdReducerResponse {
+    pub response: ReduceResponse,
+    pub index: i32,
+    pub vertex_name: &'static str,
+}
+
+impl From<UdReducerResponse> for Message {
+    fn from(wrapper: UdReducerResponse) -> Self {
+        let result = wrapper.response.result.unwrap_or_default();
+        let window = wrapper.response.window.unwrap_or_default();
+
+        // Create offset from window start and end time
+        let offset_str = format!(
+            "{}-{}",
+            utc_from_timestamp(window.start.expect("window start missing")).timestamp_millis(),
+            utc_from_timestamp(window.end.expect("window end missing")).timestamp_millis()
+        );
+
+        Message {
+            typ: Default::default(),
+            keys: Arc::from(result.keys),
+            tags: if result.tags.is_empty() {
+                None
+            } else {
+                Some(Arc::from(result.tags))
+            },
+            value: result.value.into(),
+            offset: Offset::Int(IntOffset::new(0, 0)),
+            event_time: utc_from_timestamp(window.end.unwrap()),
+            watermark: window
+                .end
+                .map(|ts| utc_from_timestamp(ts) - chrono::Duration::milliseconds(1)),
+            // this will be unique for each response which will be used for dedup (index is used because
+            // each window can have multiple reduce responses)
+            id: MessageID {
+                vertex_name: wrapper.vertex_name.into(),
+                offset: offset_str.into(),
+                index: wrapper.index,
+            },
+            ..Default::default()
+        }
+    }
+}
+
+/// User-defined aligned reduce client.
+#[derive(Clone)]
+pub(crate) struct UserDefinedAlignedReduce {
+    client: ReduceClient<Channel>,
+}
+
+impl UserDefinedAlignedReduce {
+    pub(crate) async fn new(client: ReduceClient<Channel>) -> Self {
+        UserDefinedAlignedReduce { client }
+    }
+
+    /// Calls the reduce_fn on the user-defined reducer on a separate tokio task.
+    /// If the cancellation token is triggered, it will stop processing and return early.
+    pub(crate) async fn reduce_fn(
+        &mut self,
+        stream: ReceiverStream<AlignedWindowMessage>,
+        result_tx: tokio::sync::mpsc::Sender<Message>,
+        cln_token: CancellationToken,
+    ) -> Result<()> {
+        // Convert AlignedWindowMessage stream to ReduceRequest stream
+        let (req_tx, req_rx) = tokio::sync::mpsc::channel(100);
+
+        // Spawn a task to convert AlignedWindowMessages to ReduceRequests and send them to req_tx
+        // NOTE: - This is not really required (for client side streaming reduce), we do this because
+        //         `tonic` does not return a stream from the reduce_fn unless there is some output.
+        //       - This implementation works for reduce bidi streaming.
+        let request_handle = tokio::spawn(async move {
+            let mut stream = stream;
+            while let Some(window_msg) = stream.next().await {
+                let reduce_req: ReduceRequest = window_msg.into();
+                if req_tx.send(reduce_req).await.is_err() {
+                    break;
+                }
+            }
+        });
+
+        // Call the gRPC reduce_fn with the converted stream, but also watch for cancellation
+        let mut response_stream = tokio::select! {
+            // Wait for the gRPC call to complete
+            result = self.client.reduce_fn(ReceiverStream::new(req_rx)) => {
+                result
+                    .map_err(|e| crate::Error::Reduce(format!("failed to call reduce_fn: {}", e)))?
+                    .into_inner()
+            }
+
+            // Check for cancellation
+            _ = cln_token.cancelled() => {
+                info!("Cancellation detected while waiting for reduce_fn response");
+                request_handle.abort();
+                return Err(crate::Error::Cancelled());
+            }
+        };
+
+        // Process the response stream
+        let vertex_name = get_vertex_name();
+        let mut index = 0;
+
+        loop {
+            tokio::select! {
+                // Check for cancellation
+                _ = cln_token.cancelled() => {
+                    info!("Cancellation detected while processing responses, stopping");
+                    request_handle.abort();
+                    return Err(crate::Error::Cancelled());
+                }
+
+                // Process next response
+                response = response_stream.message() => {
+                    let response = response.map_err(|e| crate::Error::Reduce(format!("failed to receive response: {}", e)))?;
+                    let Some(response) = response else {
+                        break;
+                    };
+
+                    if response.eof {
+                        break;
+                    }
+
+                    // convert to Message so it can be sent to the ISB write channel
+                    let message: Message = UdReducerResponse {
+                        response,
+                        index,
+                        vertex_name,
+                    }
+                    .into();
+
+                    result_tx
+                        .send(message)
+                        .await
+                        .expect("failed to send response");
+
+                    index += 1;
+                }
+            }
+        }
+
+        // wait for the tokio task to complete
+        request_handle
+            .await
+            .map_err(|e| crate::Error::Reduce(format!("conversion task failed: {}", e)))
+    }
+
+    pub(crate) async fn ready(&mut self) -> bool {
+        match self.client.is_ready(tonic::Request::new(())).await {
+            Ok(response) => response.into_inner().ready,
+            Err(_) => false,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use std::sync::Arc;
+    use std::time::Duration;
+
+    use chrono::{TimeZone, Utc};
+    use numaflow::reduce;
+    use numaflow_pb::clients::reduce::reduce_client::ReduceClient;
+    use tempfile::TempDir;
+    use tokio::sync::mpsc;
+    use tokio_stream::wrappers::ReceiverStream;
+    use tokio_util::sync::CancellationToken;
+
+    use super::*;
+    use crate::message::{MessageID, StringOffset};
+    use crate::reduce::reducer::aligned::windower::{Window, WindowOperation};
+    use crate::shared::grpc::create_rpc_channel;
+
+    struct Counter {}
+
+    struct CounterCreator {}
+
+    impl reduce::ReducerCreator for CounterCreator {
+        type R = Counter;
+
+        fn create(&self) -> Self::R {
+            Counter::new()
+        }
+    }
+
+    impl Counter {
+        fn new() -> Self {
+            Self {}
+        }
+    }
+
+    #[tonic::async_trait]
+    impl reduce::Reducer for Counter {
+        async fn reduce(
+            &self,
+            keys: Vec<String>,
+            mut input: mpsc::Receiver<reduce::ReduceRequest>,
+            _md: &reduce::Metadata,
+        ) -> Vec<reduce::Message> {
+            let mut counter = 0;
+            // the loop exits when input is closed which will happen only on close of book.
+            while input.recv().await.is_some() {
+                counter += 1;
+            }
+            vec![reduce::Message::new(counter.to_string().into_bytes()).with_keys(keys.clone())]
+        }
+    }
+
+    #[tokio::test]
+    async fn test_reduce_operations() -> Result<()> {
+        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("reduce.sock");
+        let server_info_file = tmp_dir.path().join("reduce-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let handle = tokio::spawn(async move {
+            reduce::Server::new(CounterCreator {})
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(shutdown_rx)
+                .await
+                .expect("server failed");
+        });
+
+        // wait for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        let mut client =
+            UserDefinedAlignedReduce::new(ReduceClient::new(create_rpc_channel(sock_file).await?))
+                .await;
+
+        // Create a window
+        let window_start = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
+        let window_end = Utc.with_ymd_and_hms(2023, 1, 1, 0, 1, 0).unwrap();
+        let window = Window {
+            start_time: window_start,
+            end_time: window_end,
+            id: "2".into(),
+        };
+
+        // Create messages
+        let messages = vec![
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value1".into(),
+                offset: Offset::String(StringOffset::new("0".to_string(), 0)),
+                event_time: window_start + chrono::Duration::seconds(10),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "0".to_string().into(),
+                    index: 0,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value2".into(),
+                offset: Offset::String(StringOffset::new("1".to_string(), 1)),
+                event_time: window_start + chrono::Duration::seconds(20),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "1".to_string().into(),
+                    index: 1,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value3".into(),
+                offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+                event_time: window_start + chrono::Duration::seconds(30),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "2".to_string().into(),
+                    index: 2,
+                },
+                ..Default::default()
+            },
+        ];
+
+        // Create window messages
+        let window_messages = vec![
+            // Open window
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Open(messages[0].clone()),
+            },
+            // Append messages
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(messages[1].clone()),
+            },
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(messages[2].clone()),
+            },
+        ];
+
+        // Create a channel to send window messages
+        let (window_tx, window_rx) = mpsc::channel(10);
+        for msg in window_messages {
+            window_tx.send(msg).await.unwrap();
+        }
+        drop(window_tx); // Close the channel to signal end of input
+
+        // Create a channel to receive results
+        let (result_tx, mut result_rx) = mpsc::channel(10);
+
+        // Create a cancellation token
+        let cln_token = CancellationToken::new();
+
+        // Call reduce_fn
+        let reduce_handle = tokio::spawn(async move {
+            client
+                .reduce_fn(ReceiverStream::new(window_rx), result_tx, cln_token)
+                .await
+                .expect("reduce_fn failed");
+        });
+
+        // Wait for the result
+        let result = tokio::time::timeout(Duration::from_secs(5), result_rx.recv())
+            .await
+            .expect("timeout waiting for result")
+            .expect("no result received");
+
+        // Verify the result
+        assert_eq!(result.keys.to_vec(), vec!["key1"]);
+        assert_eq!(String::from_utf8(result.value.to_vec()).unwrap(), "3"); // Counter should be 3
+
+        // Shutdown the server
+        shutdown_tx
+            .send(())
+            .expect("failed to send shutdown signal");
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        // Wait for the reduce handle to complete
+        reduce_handle.await.expect("reduce handle failed");
+
+        // Wait for the server to shut down
+        assert!(
+            handle.is_finished(),
+            "Expected gRPC server to have shut down"
+        );
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn reduce_operations_with_multiple_keys() -> Result<()> {
+        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("reduce_multi.sock");
+        let server_info_file = tmp_dir.path().join("reduce_multi-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let handle = tokio::spawn(async move {
+            reduce::Server::new(CounterCreator {})
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(shutdown_rx)
+                .await
+                .expect("server failed");
+        });
+
+        // wait for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        let mut client =
+            UserDefinedAlignedReduce::new(ReduceClient::new(create_rpc_channel(sock_file).await?))
+                .await;
+
+        // Create two windows for different keys
+        let window_start = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
+        let window_end = Utc.with_ymd_and_hms(2023, 1, 1, 0, 1, 0).unwrap();
+        let window = Window {
+            start_time: window_start,
+            end_time: window_end,
+            id: "0".to_string().into(),
+        };
+
+        // Create messages for key1
+        let key1_messages = vec![
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value1".into(),
+                offset: Offset::String(StringOffset::new("0".to_string(), 0)),
+                event_time: window_start + chrono::Duration::seconds(10),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "0".to_string().into(),
+                    index: 0,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value2".into(),
+                offset: Offset::String(StringOffset::new("1".to_string(), 1)),
+                event_time: window_start + chrono::Duration::seconds(20),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "1".to_string().into(),
+                    index: 1,
+                },
+                ..Default::default()
+            },
+        ];
+
+        // Create messages for key2
+        let key2_messages = vec![
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key2".into()]),
+                tags: None,
+                value: "value3".into(),
+                offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+                event_time: window_start + chrono::Duration::seconds(30),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "2".to_string().into(),
+                    index: 2,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key2".into()]),
+                tags: None,
+                value: "value4".into(),
+                offset: Offset::String(StringOffset::new("3".to_string(), 3)),
+                event_time: window_start + chrono::Duration::seconds(40),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "3".to_string().into(),
+                    index: 3,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key2".into()]),
+                tags: None,
+                value: "value5".into(),
+                offset: Offset::String(StringOffset::new("4".to_string(), 4)),
+                event_time: window_start + chrono::Duration::seconds(50),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "4".to_string().into(),
+                    index: 4,
+                },
+                ..Default::default()
+            },
+        ];
+
+        // Create window messages
+        let window_messages = vec![
+            // Open window for key1
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Open(key1_messages[0].clone()),
+            },
+            // Append message for key1
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(key1_messages[1].clone()),
+            },
+            // Open window for key2
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Open(key2_messages[0].clone()),
+            },
+            // Append messages for key2
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(key2_messages[1].clone()),
+            },
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(key2_messages[2].clone()),
+            },
+        ];
+
+        // Create a channel to send window messages
+        let (window_tx, window_rx) = mpsc::channel(100);
+        for msg in window_messages {
+            window_tx.send(msg).await.unwrap();
+        }
+        drop(window_tx); // Close the channel to signal end of input
+
+        // Create a channel to receive results
+        let (result_tx, mut result_rx) = mpsc::channel(100);
+
+        // Create a cancellation token
+        let cln_token = CancellationToken::new();
+
+        // Call reduce_fn
+        let reduce_handle = tokio::spawn(async move {
+            client
+                .reduce_fn(ReceiverStream::new(window_rx), result_tx, cln_token)
+                .await
+                .expect("reduce_fn failed");
+        });
+
+        // Collect all results
+        let mut results = Vec::new();
+        while let Some(result) = tokio::time::timeout(Duration::from_secs(2), result_rx.recv())
+            .await
+            .expect("timeout waiting for result")
+        {
+            results.push(result);
+        }
+
+        // Verify the results
+        assert_eq!(results.len(), 2);
+
+        // Sort results by key for deterministic testing
+        results.sort_by(|a, b| a.keys.to_vec().cmp(&b.keys.to_vec()));
+
+        // Check key1 result
+        assert_eq!(results[0].keys.to_vec(), vec!["key1"]);
+        assert_eq!(String::from_utf8(results[0].value.to_vec()).unwrap(), "2"); // Counter should be 2
+
+        // Check key2 result
+        assert_eq!(results[1].keys.to_vec(), vec!["key2"]);
+        assert_eq!(String::from_utf8(results[1].value.to_vec()).unwrap(), "3"); // Counter should be 3
+
+        // Shutdown the server
+        shutdown_tx
+            .send(())
+            .expect("failed to send shutdown signal");
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        // Wait for the reduce handle to complete
+        reduce_handle.await.expect("reduce handle failed");
+
+        // Wait for the server to shut down
+        assert!(
+            handle.is_finished(),
+            "Expected gRPC server to have shut down"
+        );
+
+        Ok(())
+    }
+
+    #[tokio::test]
+    async fn reduce_operations_with_sliding_window() -> Result<()> {
+        let (shutdown_tx, shutdown_rx) = tokio::sync::oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("reduce_sliding.sock");
+        let server_info_file = tmp_dir.path().join("reduce_sliding-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let handle = tokio::spawn(async move {
+            reduce::Server::new(CounterCreator {})
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(shutdown_rx)
+                .await
+                .expect("server failed");
+        });
+
+        // wait for the server to start
+        tokio::time::sleep(Duration::from_millis(100)).await;
+
+        let mut client =
+            UserDefinedAlignedReduce::new(ReduceClient::new(create_rpc_channel(sock_file).await?))
+                .await;
+
+        // Create a sliding window
+        let window_start = Utc.with_ymd_and_hms(2023, 1, 1, 0, 0, 0).unwrap();
+        let window_end = Utc.with_ymd_and_hms(2023, 1, 1, 0, 1, 0).unwrap();
+        let window = Window {
+            start_time: window_start,
+            end_time: window_end,
+            id: "1".into(),
+        };
+
+        // Create messages
+        let messages = vec![
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value1".into(),
+                offset: Offset::String(StringOffset::new("0".to_string(), 0)),
+                event_time: window_start + chrono::Duration::seconds(10),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "0".to_string().into(),
+                    index: 0,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value2".into(),
+                offset: Offset::String(StringOffset::new("1".to_string(), 1)),
+                event_time: window_start + chrono::Duration::seconds(20),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "1".to_string().into(),
+                    index: 1,
+                },
+                ..Default::default()
+            },
+            Message {
+                typ: Default::default(),
+                keys: Arc::from(vec!["key1".into()]),
+                tags: None,
+                value: "value3".into(),
+                offset: Offset::String(StringOffset::new("2".to_string(), 2)),
+                event_time: window_start + chrono::Duration::seconds(30),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex_name".to_string().into(),
+                    offset: "2".to_string().into(),
+                    index: 2,
+                },
+                ..Default::default()
+            },
+        ];
+
+        // Create window messages
+        let window_messages = vec![
+            // Open window
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Open(messages[0].clone()),
+            },
+            // Append messages
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(messages[1].clone()),
+            },
+            AlignedWindowMessage {
+                window: window.clone(),
+                operation: WindowOperation::Append(messages[2].clone()),
+            },
+        ];
+
+        // Create a channel to send window messages
+        let (window_tx, window_rx) = mpsc::channel(100);
+        for msg in window_messages {
+            window_tx.send(msg).await.unwrap();
+        }
+        drop(window_tx); // Close the channel to signal end of input
+
+        // Create a channel to receive results
+        let (result_tx, mut result_rx) = mpsc::channel(100);
+
+        // Create a cancellation token
+        let cln_token = CancellationToken::new();
+
+        // Call reduce_fn
+        let reduce_handle = tokio::spawn(async move {
+            client
+                .reduce_fn(ReceiverStream::new(window_rx), result_tx, cln_token)
+                .await
+                .expect("reduce_fn failed");
+        });
+
+        // Wait for the result
+        let result = tokio::time::timeout(Duration::from_secs(2), result_rx.recv())
+            .await
+            .expect("timeout waiting for result")
+            .expect("no result received");
+
+        // Verify the result
+        assert_eq!(result.keys.to_vec(), vec!["key1"]);
+        assert_eq!(String::from_utf8(result.value.to_vec()).unwrap(), "3"); // Counter should be 3
+
+        // Shutdown the server
+        shutdown_tx
+            .send(())
+            .expect("failed to send shutdown signal");
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        // Wait for the reduce handle to complete
+        reduce_handle.await.expect("reduce handle failed");
+
+        // Wait for the server to shut down
+        assert!(
+            handle.is_finished(),
+            "Expected gRPC server to have shut down"
+        );
+
+        Ok(())
+    }
+}
diff --git a/rust/numaflow-core/src/reduce/reducer/aligned/windower.rs b/rust/numaflow-core/src/reduce/reducer/aligned/windower.rs
new file mode 100644
index 00000000..de23f767
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/aligned/windower.rs
@@ -0,0 +1,203 @@
+//! Windower is responsible for managing the windows and exposes functions for assigning windows to
+//! messages and closing windows when the watermark has advanced beyond the window end time.
+//! Windows managed by the Windower are purely based on the event-time and is oblivious of the
+//! keys. The multiplexing of windows to keyed windows is done at the Reduce server (SDK).
+
+use crate::message::Message;
+use crate::shared::grpc::{prost_timestamp_from_utc, utc_from_timestamp};
+use bytes::Bytes;
+use chrono::{DateTime, DurationRound, TimeZone, Utc};
+use fixed::FixedWindowManager;
+use numaflow_pb::objects::wal::GcEvent;
+use sliding::SlidingWindowManager;
+use std::cmp::Ordering;
+
+/// Fixed Window Operations.
+pub(crate) mod fixed;
+/// Sliding Window Operations.
+pub(crate) mod sliding;
+
+// Technically we can have a trait for WindowManager and implement it for Fixed and Sliding, but
+// the generics are getting in all the way from the bootup code. Also, we do not expect any other
+// window types in the future.
+/// WindowManager enum that can be either a FixedWindowManager or a SlidingWindowManager.
+#[derive(Debug, Clone)]
+pub(crate) enum WindowManager {
+    /// Fixed window manager.
+    Fixed(FixedWindowManager),
+    /// Sliding window manager.
+    Sliding(SlidingWindowManager),
+}
+
+impl WindowManager {
+    /// Assigns windows to a message, dropping messages with event time earlier than the oldest window's start time
+    pub(crate) fn assign_windows(&self, msg: Message) -> Vec<AlignedWindowMessage> {
+        match self {
+            WindowManager::Fixed(manager) => manager.assign_windows(msg),
+            WindowManager::Sliding(manager) => manager.assign_windows(msg),
+        }
+    }
+
+    /// Closes any windows that can be closed because the Watermark has advanced beyond the window
+    /// end time.
+    pub(crate) fn close_windows(&self, watermark: DateTime<Utc>) -> Vec<AlignedWindowMessage> {
+        match self {
+            WindowManager::Fixed(manager) => manager.close_windows(watermark),
+            WindowManager::Sliding(manager) => manager.close_windows(watermark),
+        }
+    }
+
+    /// Deletes a window is called after the window is closed and GC is done.
+    pub(crate) fn delete_window(&self, window: Window) {
+        match self {
+            WindowManager::Fixed(manager) => manager.gc_window(window),
+            WindowManager::Sliding(manager) => manager.gc_window(window),
+        }
+    }
+
+    /// Returns the oldest window yet to be completed. This will be the lowest Watermark in the Vertex.
+    pub(crate) fn oldest_window(&self) -> Option<Window> {
+        match self {
+            WindowManager::Fixed(manager) => manager.oldest_window(),
+            WindowManager::Sliding(manager) => manager.oldest_window(),
+        }
+    }
+}
+
+/// A Window is represented by its start and end time. All the data which event time falls within
+/// this window will be reduced by the Reduce function associated with it. The association is via the
+/// id. The Windows when sorted are sorted by the end time.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub(crate) struct Window {
+    /// Start time of the window.
+    pub(crate) start_time: DateTime<Utc>,
+    /// End time of the window.
+    pub(crate) end_time: DateTime<Utc>,
+    /// Unique id of the reduce function for this window.
+    pub(crate) id: Bytes,
+}
+
+impl Ord for Window {
+    fn cmp(&self, other: &Self) -> Ordering {
+        // Compare based on end time
+        self.end_time.cmp(&other.end_time)
+    }
+}
+
+impl PartialOrd for Window {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl From<Window> for GcEvent {
+    fn from(value: Window) -> Self {
+        Self {
+            start_time: Some(prost_timestamp_from_utc(value.start_time)),
+            end_time: Some(prost_timestamp_from_utc(value.end_time)),
+            keys: vec![],
+        }
+    }
+}
+
+impl From<&Window> for numaflow_pb::objects::wal::Window {
+    fn from(window: &Window) -> Self {
+        Self {
+            start_time: Some(prost_timestamp_from_utc(window.start_time)),
+            end_time: Some(prost_timestamp_from_utc(window.end_time)),
+        }
+    }
+}
+
+impl From<&numaflow_pb::objects::wal::Window> for Window {
+    fn from(proto: &numaflow_pb::objects::wal::Window) -> Self {
+        let start_time = proto.start_time.as_ref().map(|ts| utc_from_timestamp(*ts));
+        let end_time = proto.end_time.as_ref().map(|ts| utc_from_timestamp(*ts));
+
+        Self::new(
+            start_time.expect("start time should be present"),
+            end_time.expect("end time should be present"),
+        )
+    }
+}
+
+impl Window {
+    /// Creates a new Window.
+    pub(crate) fn new(start_time: DateTime<Utc>, end_time: DateTime<Utc>) -> Self {
+        Self {
+            start_time,
+            end_time,
+            id: format!(
+                "{}-{}",
+                start_time.timestamp_millis(),
+                end_time.timestamp_millis(),
+            )
+            .into(),
+        }
+    }
+
+    /// Returns the slot for the PNF.
+    pub(crate) fn pnf_slot(&self) -> Bytes {
+        self.id.clone()
+    }
+}
+
+/// Window operations that can be performed on a [Window]. It is derived from the [Message] and the
+/// window kind.
+#[derive(Debug, Clone)]
+pub(crate) enum WindowOperation {
+    /// Open is create a new Window (Open the Book).
+    Open(Message),
+    /// Close operation for the [Window] (Close of Book). Only the window on the SDK side will be closed,
+    /// other windows for the same partition can be open.
+    Close,
+    /// Append inserts more data into the opened Window.
+    Append(Message),
+}
+
+/// Aligned Window Message.
+#[derive(Debug, Clone)]
+pub(crate) struct AlignedWindowMessage {
+    pub(crate) operation: WindowOperation,
+    pub(crate) window: Window,
+}
+
+/// Truncates a timestamp to the nearest multiple of the given duration.
+pub(crate) fn truncate_to_duration(timestamp_millis: i64, duration_millis: i64) -> i64 {
+    // Convert timestamp to DateTime
+    let dt = Utc.timestamp_millis_opt(timestamp_millis).unwrap();
+    // Convert duration_millis to TimeDelta
+    let duration = chrono::TimeDelta::try_milliseconds(duration_millis)
+        .expect("Failed to convert duration to TimeDelta");
+    // Use DurationRound to truncate
+    let truncated = dt
+        .duration_trunc(duration)
+        .expect("Failed to truncate timestamp");
+
+    // Return as milliseconds
+    truncated.timestamp_millis()
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_truncate_to_duration() {
+        // Test with timestamp 100 and duration 30
+        let result = truncate_to_duration(100, 30);
+        // Expected result: 90
+        // Explanation: 100 milliseconds truncated to the nearest multiple of 30 milliseconds
+        // should be 90 (3 * 30 = 90)
+        assert_eq!(result, 90);
+
+        // Additional test cases for verification
+        assert_eq!(truncate_to_duration(59, 30), 30);
+        assert_eq!(truncate_to_duration(60, 30), 60);
+        assert_eq!(truncate_to_duration(61, 30), 60);
+        assert_eq!(truncate_to_duration(89, 30), 60);
+        assert_eq!(truncate_to_duration(90, 30), 90);
+
+        assert_eq!(truncate_to_duration(810, 70), 770);
+    }
+}
diff --git a/rust/numaflow-core/src/reduce/reducer/aligned/windower/fixed.rs b/rust/numaflow-core/src/reduce/reducer/aligned/windower/fixed.rs
new file mode 100644
index 00000000..2cbc1a89
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/aligned/windower/fixed.rs
@@ -0,0 +1,415 @@
+//! In [Fixed Window] each event belongs to exactly one window and we can assign the message to the window
+//! directly using the event time after rounding it to the nearest window [boundary](windower::truncate_to_duration).
+//! The window is defined by the length of the window. The window is aligned to the epoch. For example,
+//! if the window length is 30s, and the event time is 100, then the window this event belongs to will
+//! be `[90, 120)`. There will be a [WAL] for each window. The GC (garbage collection) of WAL is
+//! very simple since each window has its own WAL and the GC can be done by deleting the WAL. During
+//! startup, we will replay the WALs and recreate each per WAL and simply replay the messages to that window.
+//!
+//! [Fixed Window]: https://numaflow.numaproj.io/user-guide/user-defined-functions/reduce/windowing/fixed/
+//! [WAL]: crate::reduce::wal
+
+use std::collections::BTreeSet;
+use std::sync::{Arc, RwLock};
+use std::time::Duration;
+
+use crate::message::Message;
+use crate::reduce::reducer::aligned::windower;
+use crate::reduce::reducer::aligned::windower::{AlignedWindowMessage, Window, WindowOperation};
+use chrono::{DateTime, TimeZone, Utc};
+
+#[derive(Debug, Clone)]
+pub(crate) struct FixedWindowManager {
+    /// Duration of each window
+    window_length: Duration,
+    /// Active windows sorted by end time.
+    active_windows: Arc<RwLock<BTreeSet<Window>>>,
+    /// Closed windows sorted by end time. We need to keep track of closed windows so that we can
+    /// find the oldest window computed and forwarded. The watermark is progressed based on the latest
+    /// closed window end time. The oldest window in the active_window will be greater than the latest
+    /// closed window.
+    closed_windows: Arc<RwLock<BTreeSet<Window>>>,
+}
+
+impl FixedWindowManager {
+    pub(crate) fn new(window_length: Duration) -> Self {
+        Self {
+            window_length,
+            active_windows: Arc::new(RwLock::new(BTreeSet::new())),
+            closed_windows: Arc::new(RwLock::new(BTreeSet::new())),
+        }
+    }
+
+    /// Creates a new window for the given message.
+    fn create_window(&self, msg: &Message) -> Window {
+        // Truncate event time to window length
+        let window_length_millis = self.window_length.as_millis() as i64;
+        let event_time_millis = msg.event_time.timestamp_millis();
+        let start_time_millis =
+            windower::truncate_to_duration(event_time_millis, window_length_millis);
+        let end_time_millis = start_time_millis + window_length_millis;
+
+        let start_time = Utc.timestamp_millis_opt(start_time_millis).unwrap();
+        let end_time = Utc.timestamp_millis_opt(end_time_millis).unwrap();
+
+        Window::new(start_time, end_time)
+    }
+
+    /// Assigns windows to a message
+    pub(crate) fn assign_windows(&self, msg: Message) -> Vec<AlignedWindowMessage> {
+        let window = self.create_window(&msg);
+
+        // Check if window already exists
+        let mut active_windows = self
+            .active_windows
+            .write()
+            .expect("Poisoned lock for active_windows");
+        let operation = if active_windows.contains(&window) {
+            // Window exists, append message
+            WindowOperation::Append(msg)
+        } else {
+            // New window, insert it
+            active_windows.insert(window.clone());
+            WindowOperation::Open(msg)
+        };
+
+        // Create window message
+        vec![AlignedWindowMessage { operation, window }]
+    }
+
+    /// Closes any windows that can be closed because the Watermark has advanced beyond the window
+    /// end time.
+    pub(crate) fn close_windows(&self, watermark: DateTime<Utc>) -> Vec<AlignedWindowMessage> {
+        let mut result = Vec::new();
+
+        let mut active_windows = self
+            .active_windows
+            .write()
+            .expect("Poisoned lock for active_windows");
+        let mut closed_windows = self
+            .closed_windows
+            .write()
+            .expect("Poisoned lock for closed_windows");
+
+        let mut windows_to_close = Vec::new();
+
+        for window in active_windows.iter() {
+            if window.end_time <= watermark {
+                // Create close message
+                let window_msg = AlignedWindowMessage {
+                    operation: WindowOperation::Close,
+                    window: window.clone(),
+                };
+
+                result.push(window_msg);
+                windows_to_close.push(window.clone());
+            }
+        }
+
+        // Move windows from active to closed
+        for window in windows_to_close {
+            active_windows.remove(&window);
+            closed_windows.insert(window);
+        }
+
+        result
+    }
+
+    /// Deletes a window after it is closed and GC is done.
+    pub(crate) fn gc_window(&self, window: Window) {
+        let mut closed_windows = self
+            .closed_windows
+            .write()
+            .expect("Poisoned lock for closed_windows");
+        closed_windows.remove(&window);
+    }
+
+    /// Returns the oldest window yet to be completed. This will be the lowest Watermark in the Vertex.
+    pub(crate) fn oldest_window(&self) -> Option<Window> {
+        // get the oldest window from closed_windows, if closed_windows is empty, get the oldest
+        // from active_windows
+        // NOTE: closed windows will always have a lower end time than active_windows
+        self.closed_windows
+            .read()
+            .expect("Poisoned lock for closed_windows")
+            .iter()
+            .next()
+            .cloned()
+            .or_else(|| {
+                self.active_windows
+                    .read()
+                    .expect("Poisoned lock for active_windows")
+                    .iter()
+                    .next()
+                    .cloned()
+            })
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[tokio::test]
+    async fn test_assign_windows() {
+        // Create a fixed windower with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create a test message with event time at base_time
+        let msg = Message {
+            event_time: base_time,
+            ..Default::default()
+        };
+
+        // Assign windows
+        let window_msgs = windower.assign_windows(msg.clone());
+
+        // Verify results - first message should create a new window with Open operation
+        assert_eq!(window_msgs.len(), 1);
+        // Check window boundaries
+        assert_eq!(window_msgs[0].window.start_time, base_time);
+        assert_eq!(
+            window_msgs[0].window.end_time,
+            base_time + chrono::Duration::seconds(60)
+        );
+
+        // Check operation type
+        match &window_msgs[0].operation {
+            WindowOperation::Open(_) => {}
+            _ => panic!("Expected Open operation"),
+        }
+
+        // Assign another message to the same window (base_time + 1s)
+        let msg2 = Message {
+            event_time: base_time + chrono::Duration::seconds(1),
+            ..Default::default()
+        };
+
+        let window_msgs2 = windower.assign_windows(msg2.clone());
+
+        assert_eq!(window_msgs2[0].window.start_time, base_time);
+        assert_eq!(
+            window_msgs2[0].window.end_time,
+            base_time + chrono::Duration::seconds(60)
+        );
+
+        match &window_msgs2[0].operation {
+            WindowOperation::Append(append_msg) => {
+                assert_eq!(append_msg.event_time, msg2.event_time);
+            }
+            _ => panic!("Expected Append operation"),
+        }
+    }
+
+    #[test]
+    fn test_insert_window() {
+        // Create a fixed windower with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create a window
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        // Insert window manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+        }
+
+        // Verify window exists and count is 1
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 1);
+            assert!(active_windows.contains(&window1));
+        }
+
+        // Insert the same window again (should not increase count)
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+        }
+
+        // Verify count is still 1
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 1);
+        }
+
+        // Create and insert a different window
+        let window2 = Window::new(
+            base_time + chrono::Duration::seconds(60),
+            base_time + chrono::Duration::seconds(120),
+        );
+
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window2.clone());
+        }
+
+        // Verify count is now 2
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 2);
+        }
+    }
+
+    #[test]
+    fn test_close_windows() {
+        // Create a fixed windower with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        let window2 = Window::new(
+            base_time + chrono::Duration::seconds(60),
+            base_time + chrono::Duration::seconds(120),
+        );
+
+        let window3 = Window::new(
+            base_time + chrono::Duration::seconds(120),
+            base_time + chrono::Duration::seconds(180),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // Close windows with watermark at base_time + 120s
+        let closed_msgs = windower.close_windows(base_time + chrono::Duration::seconds(120));
+
+        // Should close 2 windows (window1 and window2)
+        assert_eq!(closed_msgs.len(), 2);
+
+        assert_eq!(closed_msgs[0].window.start_time, base_time);
+        assert_eq!(
+            closed_msgs[0].window.end_time,
+            base_time + chrono::Duration::seconds(60)
+        );
+        match &closed_msgs[0].operation {
+            WindowOperation::Close => {}
+            _ => panic!("Expected Close operation"),
+        }
+
+        assert_eq!(
+            closed_msgs[1].window.start_time,
+            base_time + chrono::Duration::seconds(60)
+        );
+        assert_eq!(
+            closed_msgs[1].window.end_time,
+            base_time + chrono::Duration::seconds(120)
+        );
+        match &closed_msgs[1].operation {
+            WindowOperation::Close => {}
+            _ => panic!("Expected Close operation"),
+        }
+
+        // Verify only window3 remains in active windows
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 1);
+            assert!(active_windows.contains(&window3));
+        }
+    }
+
+    #[test]
+    fn test_delete_window() {
+        // Create a fixed windower with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        let window2 = Window::new(
+            base_time + chrono::Duration::seconds(60),
+            base_time + chrono::Duration::seconds(120),
+        );
+
+        let window3 = Window::new(
+            base_time + chrono::Duration::seconds(120),
+            base_time + chrono::Duration::seconds(180),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // close windows so that we can delete them
+        windower.close_windows(base_time + chrono::Duration::seconds(120));
+
+        assert_eq!(windower.closed_windows.read().unwrap().len(), 2);
+
+        // Delete window2
+        windower.gc_window(window2.clone());
+
+        // Verify window2 was deleted
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            let closed_windows = windower.closed_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 1);
+            assert!(!active_windows.contains(&window1));
+            assert!(!active_windows.contains(&window2));
+            assert!(active_windows.contains(&window3));
+            assert_eq!(closed_windows.len(), 1);
+            assert!(closed_windows.contains(&window1));
+            assert!(!closed_windows.contains(&window2));
+        }
+    }
+
+    #[test]
+    fn test_oldest_window_endtime() {
+        // Create a fixed windower with 60s window length
+        let windower = FixedWindowManager::new(Duration::from_secs(60));
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        let window2 = Window::new(
+            base_time + chrono::Duration::seconds(60),
+            base_time + chrono::Duration::seconds(120),
+        );
+
+        let window3 = Window::new(
+            base_time + chrono::Duration::seconds(120),
+            base_time + chrono::Duration::seconds(180),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // Verify the oldest window end time is window1's end time
+        assert_eq!(
+            windower.oldest_window().unwrap().end_time,
+            base_time + chrono::Duration::seconds(60)
+        );
+
+        // Close window1
+        windower.close_windows(base_time + chrono::Duration::seconds(60));
+
+        // Delete window1
+        windower.gc_window(window1);
+
+        // Verify the oldest window end time is now window2's end time
+        assert_eq!(
+            windower.oldest_window().unwrap().end_time,
+            base_time + chrono::Duration::seconds(120)
+        );
+    }
+}
diff --git a/rust/numaflow-core/src/reduce/reducer/aligned/windower/sliding.rs b/rust/numaflow-core/src/reduce/reducer/aligned/windower/sliding.rs
new file mode 100644
index 00000000..1db8b09a
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/aligned/windower/sliding.rs
@@ -0,0 +1,924 @@
+//! Implementation of [Sliding Window] requires a bit more bookkeeping than [Fixed Window],
+//! because a single event can belong to multiple windows, but we only have a single [WAL] (we do not
+//! have per window WALs). An example of sliding window with length 5s and slide 1s, and an event
+//! time at 100 will belong to 5 windows `[95, 100)`, `[96, 101)`, `[97, 102)`, `[98, 103)`, `[99, 104)`.
+//! This means that the element has to be to sent to multiple windows. To understand the implementation,
+//! we need to look in how we handle window creation in different scenarios (startup, normal operation,
+//! restart) but the code is oblivious to the scenario.
+//!
+//! ## Startup
+//! During pipeline startup is the very first time a pipeline is started. It has not materialized any
+//! windows yet and just started reading data from the ISB. When we read an element from the ISB with event-time
+//! of `t1`, we need to create all windows that can contain `t1`. For example, if the window length is 30s
+//! and slide is 10s, and the event-time is 100, we need to create windows `[100, 130)`, `[90, 120)`,
+//! and `[80, 110)`.
+//!
+//! ## Normal Operation
+//! During normal operation, when we read an element from the ISB, we need to create all windows that
+//! can contain the event-time of the message. For example, if the window length is 30s and slide is
+//! 10s, and the event-time is 60, we need to create windows `[60, 90)` but we can assume that
+//! the windows `[50, 80)`, and `[40, 70)` has already been created when elements with event-time
+//! 90 and 80 were received.
+//! Out-of-order can cause the assumption to fail, so we make sure that the previous windows are created
+//! if they do not exist. This can happen when we have watermark withholding, and we receive out of order
+//! messages.
+//!
+//! ## Restart
+//! When a pipeline is restarted, we will replay from the WAL to restore the state. Restart is a bit
+//! tricky because we need to make sure that we do not create duplicate windows. To do that we keep
+//! track of the min start time of the active windows. Any window with start time less than this value
+//! should not be created again.
+//! Let me explain this with an example. Let's say we have a window length of 30s and slide of 10s.
+//! and we have already processed elements with event-time 100 and 110 and the watermark has advanced to 110.
+//! So we have created windows `[100, 130)`, `[90, 120)`, `[80, 110)`, `[110, 140)`, `[100, 130)`, now
+//! let's assume that the window `[80, 110)` is closed and deleted (WM is 110). Now when we restart, we will
+//! replay the elements with event-time 100, etc. The reason for replaying element with event-time 100
+//! even if the watermark has progressed to 110, is that there are widows with start time less that the
+//! watermark. E.g., window`[100, 130)` is still open and data has to be sent to this window. This
+//! is the reason why we need the min start time of the active windows.
+//! Now to track the state of active windows across restarted, we write this information to a file on
+//! shutdown and read it on startup. In case of SIGKILL, the state file will not be created, so
+//! we will recreate the closed windows and there will be duplicates causing at-least-once
+//! behavior and not a data-loss scenario.
+//!
+//! [Sliding Window]: (https://numaflow.numaproj.io/user-guide/user-defined-functions/reduce/windowing/sliding/)
+//! [Fixed Window]: crate::reduce::reducer::aligned::windower::fixed
+//! [WAL]: crate::reduce::wal
+
+use std::collections::BTreeSet;
+use std::fs;
+use std::fs::File;
+use std::io::Write;
+use std::path::PathBuf;
+use std::sync::atomic::Ordering;
+use std::sync::{Arc, RwLock, atomic::AtomicI64};
+use std::time::Duration;
+
+use crate::message::Message;
+use crate::reduce::error::{Error, ReduceResult};
+use crate::reduce::reducer::aligned::windower::{
+    AlignedWindowMessage, Window, WindowOperation, truncate_to_duration,
+};
+use crate::shared::grpc::utc_from_timestamp;
+use chrono::{DateTime, TimeZone, Utc};
+use numaflow_pb::objects::wal::Window as ProtoWindow;
+use numaflow_pb::objects::wal::WindowManagerState;
+use prost::Message as ProtoMessage;
+use tracing::{error, info};
+
+/// Sliding window manager is responsible for managing the sliding windows. It saves the state of the
+/// active windows to a file on shutdown and loads it on startup to avoid duplicate processing.
+#[derive(Debug, Clone)]
+pub(crate) struct SlidingWindowManager {
+    /// Duration of each window.
+    window_length: Duration,
+    /// Slide duration.
+    slide: Duration,
+    /// Active windows sorted by end time. The state is saved to a file on shutdown and loaded on
+    /// startup.
+    active_windows: Arc<RwLock<BTreeSet<Window>>>,
+    /// Closed windows sorted by end time. We need to keep track of closed windows so that we can
+    /// find the oldest window computed and forwarded. The watermark is progressed based on the latest
+    /// closed window end time. The oldest window in the active_window will be greater than the latest
+    /// closed window.
+    closed_windows: Arc<RwLock<BTreeSet<Window>>>,
+    /// Optional path to save/load window state.
+    active_window_state_file: Option<PathBuf>,
+    /// max end time of the deleted windows, used to avoid creating
+    /// windows twice during replay.
+    max_deleted_window_end_time: Arc<AtomicI64>,
+}
+
+impl SlidingWindowManager {
+    /// Creates a new SlidingWindowManager and repopulates the active windows from the state file if
+    /// it exists.
+    pub(crate) fn new(
+        window_length: Duration,
+        slide: Duration,
+        state_file_path: Option<PathBuf>,
+    ) -> Self {
+        let manager = Self {
+            window_length,
+            slide,
+            active_windows: Arc::new(RwLock::new(BTreeSet::new())),
+            closed_windows: Arc::new(RwLock::new(BTreeSet::new())),
+            max_deleted_window_end_time: Arc::new(AtomicI64::new(-1)),
+            active_window_state_file: state_file_path,
+        };
+
+        // If state file path is provided, try to load state from file
+        if let Some(path) = &manager.active_window_state_file {
+            if let Err(e) = manager.load_state(path) {
+                error!("Failed to load window state from file: {}", e);
+            }
+        }
+
+        manager
+    }
+
+    /// Creates windows for the given message
+    fn create_windows(&self, msg: &Message) -> Vec<Window> {
+        let mut windows = Vec::new();
+        let window_length_millis = self.window_length.as_millis() as i64;
+        let slide_millis = self.slide.as_millis() as i64;
+        let event_time_millis = msg.event_time.timestamp_millis();
+
+        // Calculate the start time of the largest window that contains the event
+        // use the highest integer multiple of slide length which is less than the eventTime
+        // as the start time for the window. For example, if the eventTime is 810 and slide
+        // length is 70, use 770 as the startTime of the window. In that way, we can guarantee
+        // consistency while assigning the messages to the windows.
+        let start_time_millis = truncate_to_duration(event_time_millis, slide_millis);
+        let mut current_start = start_time_millis;
+        let mut current_end = current_start + window_length_millis;
+
+        // Create all windows that contain this event for example if the event time of the message
+        // is 25 with length 5s and slide 1s we will have [21, 26), [22, 27), [23, 28), [24, 29), [25, 30)
+        // windows.
+
+        // startTime and endTime will be the largest timestamp window for the given eventTime,
+        // using that we can create other windows by subtracting the slide length
+        while current_start <= event_time_millis && event_time_millis < current_end {
+            let start_time = Utc.timestamp_millis_opt(current_start).unwrap();
+            let end_time = Utc.timestamp_millis_opt(current_end).unwrap();
+
+            windows.push(Window::new(start_time, end_time));
+
+            // Move to the previous window
+            current_start -= slide_millis;
+            current_end -= slide_millis;
+        }
+
+        windows
+    }
+
+    /// Closes any windows that can be closed because the Watermark has advanced beyond the window
+    pub(crate) fn close_windows(&self, watermark: DateTime<Utc>) -> Vec<AlignedWindowMessage> {
+        let mut result = Vec::new();
+
+        // Find windows that need to be closed
+        let mut active_windows = self
+            .active_windows
+            .write()
+            .expect("Poisoned lock in active_windows");
+        let mut closed_windows = self
+            .closed_windows
+            .write()
+            .expect("Poisoned lock in active_windows");
+
+        // get all the windows that have end time less than the watermark
+        let windows_to_close: Vec<_> = active_windows
+            .iter()
+            .filter(|window| window.end_time <= watermark) // window end time is exclusive, hence <=
+            .cloned()
+            .collect();
+
+        // Move windows from active to closed
+        for window in windows_to_close {
+            result.push(AlignedWindowMessage {
+                operation: WindowOperation::Close,
+                window: window.clone(),
+            });
+            active_windows.remove(&window);
+            closed_windows.insert(window);
+        }
+
+        result
+    }
+
+    /// Deletes a window after it is closed and GC is done.
+    pub(crate) fn gc_window(&self, window: Window) {
+        // Update max_deleted_window_end_time if this window's end time is greater
+        self.max_deleted_window_end_time
+            .fetch_max(window.end_time.timestamp_millis(), Ordering::Relaxed);
+
+        // Remove the window from closed_windows
+        self.closed_windows
+            .write()
+            .expect("Poisoned lock for closed_windows")
+            .remove(&window);
+    }
+
+    /// Returns the oldest window yet to be completed. This will be the lowest Watermark in the Vertex.
+    pub(crate) fn oldest_window(&self) -> Option<Window> {
+        // get the oldest window from closed_windows, if closed_windows is empty, get the oldest
+        // from active_windows
+        // NOTE: closed windows will always have a lower end time than active_windows
+        self.closed_windows
+            .read()
+            .expect("Poisoned lock for closed_windows")
+            .iter()
+            .next()
+            .cloned()
+            .or_else(|| {
+                self.active_windows
+                    .read()
+                    .expect("Poisoned lock for active_windows")
+                    .iter()
+                    .next()
+                    .cloned()
+            })
+    }
+
+    /// Helper method to format sorted window information for logging.
+    fn format_windows_for_log(windows: &[ProtoWindow]) -> String {
+        let formatted_windows: String = windows
+            .iter()
+            .map(|window| {
+                let start_time = utc_from_timestamp(window.start_time.unwrap()).timestamp_millis();
+                let end_time = utc_from_timestamp(window.end_time.unwrap()).timestamp_millis();
+                format!("[{} - {}]", start_time, end_time)
+            })
+            .collect::<Vec<_>>()
+            .join(", ");
+
+        format!("{} windows: {}", windows.len(), formatted_windows)
+    }
+
+    /// Saves the current window state to the specified file.
+    pub(crate) fn save_state(&self) -> ReduceResult<()> {
+        let path = match &self.active_window_state_file {
+            Some(path) => path,
+            None => return Ok(()),
+        };
+
+        let all_windows: Vec<ProtoWindow> = {
+            let active_windows = self
+                .active_windows
+                .read()
+                .expect("Poisoned lock for active_windows");
+            let closed_windows = self
+                .closed_windows
+                .read()
+                .expect("Poisoned lock for closed_windows");
+
+            closed_windows
+                .iter()
+                .chain(active_windows.iter())
+                .map(Into::into)
+                .collect()
+        };
+
+        let state = WindowManagerState {
+            windows: all_windows,
+            max_deleted_window_end_time: self.max_deleted_window_end_time.load(Ordering::Relaxed),
+        };
+
+        info!(
+            path = %path.display(),
+            "Saving window state: {}",
+            Self::format_windows_for_log(&state.windows)
+        );
+
+        let mut buf = Vec::new();
+        state
+            .encode(&mut buf)
+            .map_err(|e| Error::Other(e.to_string()))?;
+
+        File::create(path)
+            .and_then(|mut file| file.write_all(&buf))
+            .map_err(|e| Error::Other(e.to_string()))?;
+
+        Ok(())
+    }
+
+    /// Loads window state from the specified file
+    fn load_state(&self, path: &PathBuf) -> ReduceResult<()> {
+        if !path.exists() {
+            info!(path = %path.display(), "Window state file does not exist");
+            return Ok(());
+        }
+
+        let buf = fs::read(path).map_err(|e| Error::Other(e.to_string()))?;
+
+        let state =
+            WindowManagerState::decode(&buf[..]).map_err(|e| Error::Other(e.to_string()))?;
+
+        self.active_windows
+            .write()
+            .expect("Poisoned lock for active_windows")
+            .extend(state.windows.iter().map(Into::into));
+
+        self.max_deleted_window_end_time
+            .store(state.max_deleted_window_end_time, Ordering::Relaxed);
+
+        info!(
+            "Loaded window state: {}",
+            Self::format_windows_for_log(&state.windows)
+        );
+
+        // remove the state file, else we will load the state file on next restart in case of crash (SIGKILL)
+        fs::remove_file(path).map_err(|e| Error::Other(e.to_string()))?;
+        info!("Removed window state file: {}", path.display());
+
+        Ok(())
+    }
+
+    /// Assigns windows to a message. It first figures all the windows that the message belongs to and
+    /// then checks if the window already exists. If it does, it appends the message to the window,
+    /// else it creates a new window and adds the message to it.
+    pub(crate) fn assign_windows(&self, msg: Message) -> Vec<AlignedWindowMessage> {
+        let windows = self.create_windows(&msg);
+        let mut result = Vec::new();
+
+        // Check if windows already exist and create appropriate operations
+        let mut active_windows = self.active_windows.write().unwrap();
+
+        for window in &windows {
+            if active_windows.contains(window) {
+                // Window exists, append message
+                result.push(AlignedWindowMessage {
+                    operation: WindowOperation::Append(msg.clone()),
+                    window: window.clone(),
+                });
+            } else if window.end_time.timestamp_millis()
+                > self.max_deleted_window_end_time.load(Ordering::Relaxed)
+            {
+                // we cannot use active_window because that could be misaligned due to out-of-order
+                // messages.
+                // only create the window if the end time is greater than the max deleted window end
+                // time, during replay we might get the same message again and again, so we need to
+                // avoid creating the materialized window again.
+                active_windows.insert(window.clone());
+                result.push(AlignedWindowMessage {
+                    operation: WindowOperation::Open(msg.clone()),
+                    window: window.clone(),
+                });
+            }
+        }
+
+        result
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use std::sync::Arc;
+
+    #[tokio::test]
+    async fn test_assign_windows_length_divisible_by_slide() {
+        // Create a sliding windower with 60s window length and 20s slide
+        let windower =
+            SlidingWindowManager::new(Duration::from_secs(60), Duration::from_secs(20), None);
+
+        // Base time: 60000ms (60s)
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create a test message with event time at base_time + 10s
+        let msg = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["test_key".to_string()]),
+            tags: None,
+            value: "test_value".as_bytes().to_vec().into(),
+            offset: Default::default(),
+            event_time: Utc
+                .timestamp_millis_opt(base_time.timestamp_millis() + 10000)
+                .unwrap(),
+            ..Default::default()
+        };
+
+        // Assign windows
+        let window_msgs = windower.assign_windows(msg.clone());
+
+        // Verify results - should be assigned to exactly 3 windows
+        assert_eq!(window_msgs.len(), 3);
+
+        // Check first window: [60000, 120000)
+        assert_eq!(window_msgs[0].window.start_time.timestamp_millis(), 60000);
+        assert_eq!(window_msgs[0].window.end_time.timestamp_millis(), 120000);
+        match &window_msgs[0].operation {
+            WindowOperation::Open(_) => {}
+            _ => panic!("Expected Open operation"),
+        }
+
+        // Assign another message to the same windows
+        let msg2 = Message {
+            event_time: Utc
+                .timestamp_millis_opt(base_time.timestamp_millis() + 1000)
+                .unwrap(),
+            ..msg.clone()
+        };
+
+        let window_msgs2 = windower.assign_windows(msg2.clone());
+
+        // Verify results - should be assigned to exactly 3 windows
+        assert_eq!(window_msgs2.len(), 3);
+
+        // All operations should be Append
+        for window_msg in &window_msgs2 {
+            match &window_msg.operation {
+                WindowOperation::Append(_) => {}
+                _ => panic!("Expected Append operation"),
+            }
+        }
+    }
+
+    #[tokio::test]
+    async fn test_assign_windows_length_not_divisible_by_slide() {
+        // Create a sliding windower with 60s window length and 40s slide
+        let windower =
+            SlidingWindowManager::new(Duration::from_secs(60), Duration::from_secs(40), None);
+
+        // Base time: 600s
+        let base_time = Utc.timestamp_opt(600, 0).unwrap();
+
+        // Create a test message with event time at base_time + 10s
+        let msg = Message {
+            typ: Default::default(),
+            keys: Arc::from(vec!["test_key".to_string()]),
+            tags: None,
+            value: "test_value".as_bytes().to_vec().into(),
+            offset: Default::default(),
+            event_time: Utc.timestamp_opt(base_time.timestamp() + 10, 0).unwrap(),
+            ..Default::default()
+        };
+
+        // Assign windows
+        let window_msgs = windower.assign_windows(msg.clone());
+
+        // Verify results - should be assigned to exactly 2 windows
+        assert_eq!(window_msgs.len(), 2);
+
+        assert_eq!(window_msgs[0].window.start_time.timestamp(), 600);
+        assert_eq!(window_msgs[0].window.end_time.timestamp(), 660);
+        match &window_msgs[0].operation {
+            WindowOperation::Open(_) => {}
+            _ => panic!("Expected Open operation"),
+        }
+
+        // Check second window: [560, 620)
+        assert_eq!(window_msgs[1].window.start_time.timestamp(), 560);
+        assert_eq!(window_msgs[1].window.end_time.timestamp(), 620);
+        match &window_msgs[1].operation {
+            WindowOperation::Open(_) => {}
+            _ => panic!("Expected Open operation"),
+        }
+
+        // Create and insert the second window manually to test append behavior
+        let window2 = Window::new(
+            Utc.timestamp_opt(560, 0).unwrap(),
+            Utc.timestamp_opt(620, 0).unwrap(),
+        );
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window2.clone());
+        }
+
+        // Assign another message to the same windows
+        let msg2 = Message {
+            event_time: Utc.timestamp_opt(base_time.timestamp() + 1, 0).unwrap(),
+            ..msg.clone()
+        };
+
+        let window_msgs2 = windower.assign_windows(msg2.clone());
+
+        // Should also be assigned to 2 windows
+        assert_eq!(window_msgs2.len(), 2);
+
+        // All operations should be Append
+        for window_msg in &window_msgs2 {
+            match &window_msg.operation {
+                WindowOperation::Append(_) => {}
+                _ => panic!("Expected Append operation"),
+            }
+        }
+    }
+
+    #[test]
+    fn test_close_windows() {
+        // Base time: 60000ms (60s)
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create a sliding windower with 60s window length and 10s slide
+        let windower =
+            SlidingWindowManager::new(Duration::from_secs(60), Duration::from_secs(10), None);
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        let window2 = Window::new(
+            base_time - chrono::Duration::seconds(10),
+            base_time + chrono::Duration::seconds(50),
+        );
+
+        let window3 = Window::new(
+            base_time - chrono::Duration::seconds(20),
+            base_time + chrono::Duration::seconds(40),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // Close windows with watermark at base_time + 120s
+        let closed = windower.close_windows(base_time + chrono::Duration::seconds(120));
+
+        // Should close all 3 windows
+        assert_eq!(closed.len(), 3);
+
+        // Check that all windows are closed in order of end time
+
+        assert_eq!(
+            closed[0].window.start_time,
+            base_time - chrono::Duration::seconds(20)
+        );
+        assert_eq!(
+            closed[0].window.end_time,
+            base_time + chrono::Duration::seconds(40)
+        );
+        match &closed[0].operation {
+            WindowOperation::Close => {}
+            _ => panic!("Expected Close operation"),
+        }
+
+        assert_eq!(
+            closed[1].window.start_time,
+            base_time - chrono::Duration::seconds(10)
+        );
+        assert_eq!(
+            closed[1].window.end_time,
+            base_time + chrono::Duration::seconds(50)
+        );
+        match &closed[1].operation {
+            WindowOperation::Close => {}
+            _ => panic!("Expected Close operation"),
+        }
+
+        assert_eq!(closed[2].window.start_time, base_time);
+        assert_eq!(
+            closed[2].window.end_time,
+            base_time + chrono::Duration::seconds(60)
+        );
+        match &closed[2].operation {
+            WindowOperation::Close => {}
+            _ => panic!("Expected Close operation"),
+        }
+
+        // Verify all windows are closed
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 0);
+        }
+    }
+
+    #[test]
+    fn test_delete_window() {
+        // Base time: 60000ms (60s)
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create a sliding windower with 60s window length and 10s slide
+        let windower =
+            SlidingWindowManager::new(Duration::from_secs(60), Duration::from_secs(10), None);
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        let window2 = Window::new(
+            base_time - chrono::Duration::seconds(10),
+            base_time + chrono::Duration::seconds(50),
+        );
+
+        let window3 = Window::new(
+            base_time - chrono::Duration::seconds(20),
+            base_time + chrono::Duration::seconds(40),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // Verify all windows exist
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 3);
+        }
+
+        // Close window1
+        windower.close_windows(base_time + chrono::Duration::seconds(60));
+
+        // Delete window1
+        windower.gc_window(window1.clone());
+
+        // Verify window1 is deleted
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 0);
+            let closed_windows = windower.closed_windows.read().unwrap();
+            assert_eq!(closed_windows.len(), 2);
+            assert!(!closed_windows.contains(&window1));
+            assert!(closed_windows.contains(&window2));
+            assert!(closed_windows.contains(&window3));
+        }
+
+        // Delete window2
+        windower.gc_window(window2.clone());
+
+        // Verify window2 is deleted
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 0);
+            assert!(!active_windows.contains(&window1));
+            assert!(!active_windows.contains(&window2));
+            assert!(!active_windows.contains(&window3));
+        }
+
+        // Delete window3
+        windower.gc_window(window3.clone());
+
+        // Verify window3 is deleted
+        {
+            let active_windows = windower.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 0);
+        }
+    }
+
+    #[test]
+    fn test_oldest_window_endtime() {
+        // Base time: 60000ms (60s)
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create a sliding windower with 60s window length and 10s slide
+        let windower =
+            SlidingWindowManager::new(Duration::from_secs(60), Duration::from_secs(10), None);
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+
+        let window2 = Window::new(
+            base_time - chrono::Duration::seconds(10),
+            base_time + chrono::Duration::seconds(50),
+        );
+
+        let window3 = Window::new(
+            base_time - chrono::Duration::seconds(20),
+            base_time + chrono::Duration::seconds(40),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // Verify oldest window end time
+        assert_eq!(
+            windower
+                .oldest_window()
+                .unwrap()
+                .end_time
+                .timestamp_millis(),
+            (base_time + chrono::Duration::seconds(40)).timestamp_millis()
+        );
+
+        // Close window3
+        windower.close_windows(base_time + chrono::Duration::seconds(40));
+
+        // Delete window3 (the oldest)
+        windower.gc_window(window3.clone());
+
+        // Verify the oldest window end time is now window2
+        assert_eq!(
+            windower
+                .oldest_window()
+                .unwrap()
+                .end_time
+                .timestamp_millis(),
+            (base_time + chrono::Duration::seconds(50)).timestamp_millis()
+        );
+
+        // Close window2
+        windower.close_windows(base_time + chrono::Duration::seconds(50));
+
+        // Delete window2
+        windower.gc_window(window2.clone());
+
+        // Verify oldest window end time is now window1
+        assert_eq!(
+            windower
+                .oldest_window()
+                .unwrap()
+                .end_time
+                .timestamp_millis(),
+            (base_time + chrono::Duration::seconds(60)).timestamp_millis()
+        );
+
+        // Close window1
+        windower.close_windows(base_time + chrono::Duration::seconds(60));
+
+        // Delete window1
+        windower.gc_window(window1.clone());
+        assert_eq!(windower.oldest_window(), None);
+    }
+
+    #[test]
+    fn test_assign_windows_with_small_slide() {
+        // prepopulate active windows
+        let active_windows = vec![
+            Window::new(
+                Utc.timestamp_millis_opt(90000).unwrap(),
+                Utc.timestamp_millis_opt(150000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(80000).unwrap(),
+                Utc.timestamp_millis_opt(140000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(70000).unwrap(),
+                Utc.timestamp_millis_opt(130000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(60000).unwrap(),
+                Utc.timestamp_millis_opt(120000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(50000).unwrap(),
+                Utc.timestamp_millis_opt(110000).unwrap(),
+            ),
+        ];
+
+        // Create a sliding windower with 60s window length and 10s slide
+        let windower = SlidingWindowManager {
+            window_length: Duration::from_secs(60),
+            slide: Duration::from_secs(10),
+            active_windows: Arc::new(RwLock::new(BTreeSet::from_iter(
+                active_windows.iter().cloned(),
+            ))),
+            closed_windows: Arc::new(RwLock::new(BTreeSet::new())),
+            active_window_state_file: None,
+            max_deleted_window_end_time: Arc::new(Default::default()),
+        };
+
+        // Create a test message with event time at 105s
+        let msg = Message {
+            event_time: Utc.timestamp_millis_opt(105000).unwrap(),
+            ..Default::default()
+        };
+
+        // Pre-populate expected windows
+        // For event time 105000ms (105s):
+        // Window 1: [100000, 160000) - event falls in this window
+        // Window 2: [90000, 150000) - event falls in this window
+        // Window 3: [80000, 140000) - event falls in this window
+        // Window 4: [70000, 130000) - event falls in this window
+        // Window 5: [60000, 120000) - event falls in this window
+        // Window 6: [50000, 110000) - event falls in this window
+        let expected_windows = vec![
+            Window::new(
+                Utc.timestamp_millis_opt(100000).unwrap(),
+                Utc.timestamp_millis_opt(160000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(90000).unwrap(),
+                Utc.timestamp_millis_opt(150000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(80000).unwrap(),
+                Utc.timestamp_millis_opt(140000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(70000).unwrap(),
+                Utc.timestamp_millis_opt(130000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(60000).unwrap(),
+                Utc.timestamp_millis_opt(120000).unwrap(),
+            ),
+            Window::new(
+                Utc.timestamp_millis_opt(50000).unwrap(),
+                Utc.timestamp_millis_opt(110000).unwrap(),
+            ),
+        ];
+
+        // Assign windows
+        let window_msgs = windower.assign_windows(msg.clone());
+
+        // Should also be assigned to 6 windows (5 are already open, 1 is new)
+        assert_eq!(window_msgs.len(), expected_windows.len());
+
+        // Check all windows match expected windows
+        for (i, window_msg) in window_msgs.iter().enumerate() {
+            // Check operation type
+            match &window_msg.operation {
+                WindowOperation::Open(open_msg) => {
+                    assert_eq!(open_msg.event_time, msg.event_time);
+                    assert_eq!(
+                        window_msg.window.start_time,
+                        Utc.timestamp_millis_opt(100000).unwrap()
+                    );
+                    assert_eq!(
+                        window_msg.window.end_time,
+                        Utc.timestamp_millis_opt(160000).unwrap()
+                    );
+                }
+                WindowOperation::Append(append_msg) => {
+                    assert_eq!(append_msg.event_time, msg.event_time);
+                }
+                WindowOperation::Close => {
+                    panic!("Expected Open or Append operation");
+                }
+            }
+
+            // Verify window matches expected window
+            let window = &window_msg.window;
+            assert_eq!(window.start_time, expected_windows[i].start_time);
+            assert_eq!(window.end_time, expected_windows[i].end_time);
+        }
+
+        // Assign another message to the same window
+        let msg2 = Message {
+            event_time: Utc.timestamp_millis_opt(105000).unwrap(),
+            ..Default::default()
+        };
+
+        let window_msgs_two = windower.assign_windows(msg2.clone());
+
+        // Should also be assigned to 6 windows, all append
+        assert_eq!(window_msgs_two.len(), 6);
+
+        for window_msg in &window_msgs_two {
+            // largest window should be open, rest are append
+            match &window_msg.operation {
+                WindowOperation::Append(append_msg) => {
+                    assert_eq!(append_msg.event_time, msg2.event_time);
+                }
+                WindowOperation::Close | WindowOperation::Open(_) => {
+                    panic!("Expected Append operation");
+                }
+            }
+        }
+    }
+
+    #[test]
+    fn test_save_and_load_state() {
+        use tempfile::tempdir;
+
+        // Create a temporary directory for the test
+        let temp_dir = tempdir().unwrap();
+        let state_file_path = temp_dir.path().join("window_state.bin");
+
+        // Create a sliding windower with 60s window length and 10s slide
+        let windower = SlidingWindowManager::new(
+            Duration::from_secs(60),
+            Duration::from_secs(10),
+            Some(state_file_path.clone()),
+        );
+
+        // Base time: 60000ms (60s)
+        let base_time = Utc.timestamp_millis_opt(60000).unwrap();
+
+        // Create windows
+        let window1 = Window::new(base_time, base_time + chrono::Duration::seconds(60));
+        let window2 = Window::new(
+            base_time - chrono::Duration::seconds(10),
+            base_time + chrono::Duration::seconds(50),
+        );
+        let window3 = Window::new(
+            base_time - chrono::Duration::seconds(20),
+            base_time + chrono::Duration::seconds(40),
+        );
+
+        // Insert windows manually
+        {
+            let mut active_windows = windower.active_windows.write().unwrap();
+            active_windows.insert(window1.clone());
+            active_windows.insert(window2.clone());
+            active_windows.insert(window3.clone());
+        }
+
+        // Manually save state
+        windower.save_state().unwrap();
+
+        // Verify file exists
+        assert!(state_file_path.exists());
+
+        // Create a new windower with the same state file
+        let windower2 = SlidingWindowManager::new(
+            Duration::from_secs(60),
+            Duration::from_secs(10),
+            Some(state_file_path.clone()),
+        );
+
+        // Verify windows were loaded
+        {
+            let active_windows = windower2.active_windows.read().unwrap();
+            assert_eq!(active_windows.len(), 3);
+            assert!(active_windows.contains(&window1));
+            assert!(active_windows.contains(&window2));
+            assert!(active_windows.contains(&window3));
+        }
+
+        // Clean up
+        temp_dir.close().unwrap();
+    }
+}
diff --git a/rust/numaflow-core/src/reduce/reducer/user_defined.rs b/rust/numaflow-core/src/reduce/reducer/user_defined.rs
new file mode 100644
index 00000000..01c22061
--- /dev/null
+++ b/rust/numaflow-core/src/reduce/reducer/user_defined.rs
@@ -0,0 +1,8 @@
+//! User-defined Reduce for Aligned and Unaligned Windows.
+
+use crate::reduce::reducer::aligned::user_defined;
+
+#[derive(Clone)]
+pub(crate) enum ReduceClient {
+    Aligned(user_defined::UserDefinedAlignedReduce),
+}
diff --git a/rust/numaflow-core/src/reduce/wal.rs b/rust/numaflow-core/src/reduce/wal.rs
index b809b979..b07463d1 100644
--- a/rust/numaflow-core/src/reduce/wal.rs
+++ b/rust/numaflow-core/src/reduce/wal.rs
@@ -91,8 +91,7 @@ impl TryFrom<Bytes> for WalMessage {
                 .expect("event time should be present"),
             watermark: proto_read_message.watermark.map(utc_from_timestamp),
             id: header.id.map(Into::into).unwrap_or_default(),
-            headers: header.headers,
-            metadata: None,
+            ..Default::default()
         };
 
         Ok(WalMessage { message: msg })
diff --git a/rust/numaflow-core/src/reduce/wal/segment.rs b/rust/numaflow-core/src/reduce/wal/segment.rs
index af3c0f5d..52a5046a 100644
--- a/rust/numaflow-core/src/reduce/wal/segment.rs
+++ b/rust/numaflow-core/src/reduce/wal/segment.rs
@@ -87,12 +87,12 @@ impl std::fmt::Display for WalType {
 #[derive(Debug)]
 pub(crate) struct GcEventEntry {
     /// Start time of the Window
-    start_time: DateTime<Utc>,
+    pub(crate) start_time: DateTime<Utc>,
     /// The end time of the Window. Anything after this timestamp has not been processed.
-    end_time: DateTime<Utc>,
+    pub(crate) end_time: DateTime<Utc>,
     /// Keys are used only for Unaligned window since sessions are defined not purely on [crate::watermark]
     /// but also on Keys.
-    keys: Option<Vec<String>>,
+    pub(crate) keys: Option<Vec<String>>,
 }
 
 impl From<GcEvent> for GcEventEntry {
@@ -133,8 +133,10 @@ mod tests {
     use bytes::Bytes;
     use chrono::{TimeZone, Utc};
     use std::sync::Arc;
+    use std::time::Duration;
     use tokio_stream::StreamExt;
     use tokio_stream::wrappers::ReceiverStream;
+    use tokio_util::sync::CancellationToken;
 
     #[tokio::test]
     async fn test_gc_wal_and_aligned_compaction() {
@@ -147,6 +149,7 @@ mod tests {
             1,    // 1MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // 5 minutes
         )
         .await
         .unwrap();
@@ -184,6 +187,7 @@ mod tests {
             1,    // 20MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // 5 minutes
         )
         .await
         .unwrap();
@@ -223,21 +227,25 @@ mod tests {
         handle.await.unwrap().unwrap();
 
         // Create and run compactor
-        let compactor = Compactor::new(
-            test_path.clone(),
-            WindowKind::Aligned,
-            1,    // 20MB
-            1000, // 1s flush interval
-            500,  // channel buffer
-        )
-        .await
-        .unwrap();
+        let compactor = Compactor::new(test_path.clone(), WindowKind::Aligned, 1, 1000, 500, 300)
+            .await
+            .unwrap();
+
+        let (replay_tx, _replay_rx) = tokio::sync::mpsc::channel(1000);
+        let cln_token = CancellationToken::new();
 
-        compactor.compact(None).await.unwrap();
+        let handle = compactor
+            .start_compaction_with_replay(replay_tx, Duration::from_secs(60), cln_token.clone())
+            .await
+            .unwrap();
+
+        // signal the compaction task to stop and wait for it to complete
+        cln_token.cancel();
+        handle.await.unwrap().unwrap();
 
         // Verify compacted data
-        let compaction_wal = ReplayWal::new(WalType::Compact, test_path);
-        let (mut rx, handle) = compaction_wal.streaming_read().unwrap();
+        let compaction_replay_wal = ReplayWal::new(WalType::Compact, test_path);
+        let (mut rx, handle) = compaction_replay_wal.streaming_read().unwrap();
 
         let mut remaining_message_count = 0;
         while let Some(entry) = rx.next().await {
@@ -248,8 +256,8 @@ mod tests {
                     if let Some(message_info) = header.message_info {
                         let event_time = message_info.event_time.map(utc_from_timestamp).unwrap();
                         assert!(
-                            event_time > gc_end,
-                            "Found message with event_time <= gc_end"
+                            event_time >= gc_end,
+                            "Found message with event_time < gc_end"
                         );
                     }
                 }
@@ -258,11 +266,11 @@ mod tests {
         }
         handle.await.unwrap().unwrap();
 
-        // we send 100 messages out of which 10 messages have event times less than the window end
-        // time so the remaining message cound should be 90
+        // we send 100 messages out of which 9 messages have event times less than the window end
+        // time so the remaining message cound should be 91
         assert_eq!(
-            remaining_message_count, 90,
-            "Expected 90 messages to remain after compaction"
+            remaining_message_count, 91,
+            "Expected 91 messages to remain after compaction"
         );
     }
 
@@ -277,6 +285,7 @@ mod tests {
             1,    // 1MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // 5 minutes
         )
         .await
         .unwrap();
@@ -329,6 +338,7 @@ mod tests {
             1,    // 20MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // 5 minutes
         )
         .await
         .unwrap();
@@ -379,11 +389,22 @@ mod tests {
             1,    // 20MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // max_segment_age_secs
         )
         .await
         .unwrap();
 
-        compactor.compact(None).await.unwrap();
+        let cln_token = CancellationToken::new();
+        let (replay_tx, _replay_rx) = tokio::sync::mpsc::channel(1000);
+
+        let handle = compactor
+            .start_compaction_with_replay(replay_tx, Duration::from_secs(60), cln_token.clone())
+            .await
+            .unwrap();
+
+        // signal the compaction task to stop and wait for it to complete
+        cln_token.cancel();
+        handle.await.unwrap().unwrap();
 
         // Verify compacted data
         let compaction_wal = ReplayWal::new(WalType::Compact, test_path);
@@ -405,14 +426,14 @@ mod tests {
                         if keys.contains(&"key1".to_string()) {
                             key1_count += 1;
                             assert!(
-                                event_time > gc_end_key1,
-                                "Found key1 message with event_time <= gc_end_key1"
+                                event_time >= gc_end_key1,
+                                "Found key1 message with event_time < gc_end_key1"
                             );
                         } else if keys.contains(&"key2".to_string()) {
                             key2_count += 1;
                             assert!(
-                                event_time > gc_end_key2,
-                                "Found key2 message with event_time <= gc_end_key2"
+                                event_time >= gc_end_key2,
+                                "Found key2 message with event_time < gc_end_key2"
                             );
                         }
                     }
@@ -422,17 +443,17 @@ mod tests {
         }
         handle.await.unwrap().unwrap();
 
-        // For key1, we should have removed messages with event time <= 10s (10 messages)
-        // For key2, we should have removed messages with event time <= 15s (15 messages)
-        // Total messages: 100 - (5 key1 messages + 8 key2 messages) = 87
+        // For key1, we should have removed messages with event time < 10s (9 messages)
+        // For key2, we should have removed messages with event time < 15s (15 messages)
+        // Total messages: 100 - (4 key1 messages + 7 key2 messages) = 87
         // Note: Since we alternate keys and start from 1, key1 is at odd indices and key2 at even indices
         assert_eq!(
-            remaining_message_count, 87,
-            "Expected 87 messages to remain after compaction"
+            remaining_message_count, 89,
+            "Expected 89 messages to remain after compaction"
         );
 
-        // We expect 45 key1 messages (50 - 5) and 40 key2 messages (50 - 8)
-        assert_eq!(key1_count, 45, "Expected 45 key1 messages to remain");
-        assert_eq!(key2_count, 42, "Expected 42 key2 messages to remain");
+        // We expect 45 key1 messages (50 - 4) and 40 key2 messages (50 - 7)
+        assert_eq!(key1_count, 46, "Expected 46 key1 messages to remain");
+        assert_eq!(key2_count, 43, "Expected 43 key2 messages to remain");
     }
 }
diff --git a/rust/numaflow-core/src/reduce/wal/segment/append.rs b/rust/numaflow-core/src/reduce/wal/segment/append.rs
index 28931987..415eeabb 100644
--- a/rust/numaflow-core/src/reduce/wal/segment/append.rs
+++ b/rust/numaflow-core/src/reduce/wal/segment/append.rs
@@ -4,7 +4,7 @@ use crate::reduce::wal::segment::WalType;
 use bytes::Bytes;
 use chrono::{DateTime, Utc};
 use std::path::Path;
-use std::{io, path::PathBuf};
+use std::path::PathBuf;
 use tokio::io::BufWriter;
 use tokio::task::JoinHandle;
 use tokio::{
@@ -56,6 +56,8 @@ struct SegmentWriteActor {
     flush_interval: Duration,
     /// Maximum file size per segment.
     max_file_size: u64,
+    /// Maximum age of a segment file before rotation
+    max_segment_age: chrono::Duration,
     /// The result of [SegmentWriteMessage] operation.
     result_tx: Sender<Offset>,
 }
@@ -77,6 +79,7 @@ impl SegmentWriteActor {
         current_file: BufWriter<File>,
         max_file_size: u64,
         flush_interval: Duration,
+        max_segment_age: chrono::Duration,
         result_tx: Sender<Offset>,
     ) -> Self {
         Self {
@@ -89,6 +92,7 @@ impl SegmentWriteActor {
             file_index: 0,
             max_file_size,
             flush_interval,
+            max_segment_age,
             result_tx,
         }
     }
@@ -162,7 +166,24 @@ impl SegmentWriteActor {
     /// This is not Cancel Safe. We can make it cancel safe by removing the buffering and instead of
     /// `write_all`, we should use `write`.
     async fn write_data(&mut self, data: Bytes) -> WalResult<()> {
+        // Check if we need to rotate based on size
         if self.current_size > 0 && self.current_size + data.len() as u64 >= self.max_file_size {
+            debug!(
+                current_size = self.current_size,
+                max_size = self.max_file_size,
+                "Rotating segment file due to size threshold"
+            );
+            self.rotate_file(true).await?;
+        }
+
+        // Check if we need to rotate based on time
+        if self.current_size > 0
+            && Utc::now().signed_duration_since(self.create_time) > self.max_segment_age
+        {
+            debug!(
+                max_age = ?self.max_segment_age,
+                "Rotating segment file due to age threshold"
+            );
             self.rotate_file(true).await?;
         }
 
@@ -189,16 +210,14 @@ impl SegmentWriteActor {
         base_path: &Path,
         idx: usize,
     ) -> WalResult<(String, BufWriter<File>)> {
-        let timestamp_nanos = Utc::now()
-            .timestamp_nanos_opt()
-            .unwrap_or(Utc::now().timestamp_micros());
+        let timestamp = Utc::now().timestamp_micros();
 
         // the name is important. sorting is based on the timestamp and then on file-index.
         let filename = format!(
             "{}_{}_{}.wal{}",
             wal_type.segment_prefix(),
             idx,
-            timestamp_nanos,
+            timestamp,
             wal_type.segment_suffix()
         );
 
@@ -252,6 +271,7 @@ impl SegmentWriteActor {
                 .trim_end_matches(&self.wal_type.segment_suffix());
             format!("{}.frozen", to_file_name)
         };
+
         tokio::fs::rename(&self.current_file_name, &to_file_name).await?;
         info!(?self.current_file_name, ?to_file_name, "rename successful");
 
@@ -266,7 +286,6 @@ impl SegmentWriteActor {
             self.create_time = Utc::now();
             self.current_size = 0;
         }
-
         Ok(())
     }
 }
@@ -278,6 +297,7 @@ pub(crate) struct AppendOnlyWal {
     base_path: PathBuf,
     max_file_size_mb: u64,
     flush_interval_ms: u64,
+    max_segment_age_secs: u64,
     channel_buffer_size: usize,
 }
 
@@ -289,13 +309,15 @@ impl AppendOnlyWal {
         max_file_size_mb: u64,
         flush_interval_ms: u64,
         channel_buffer_size: usize,
-    ) -> Result<Self, io::Error> {
+        max_segment_age_secs: u64,
+    ) -> WalResult<Self> {
         tokio::fs::create_dir_all(&base_path).await?;
         Ok(Self {
             wal_type,
             base_path,
             max_file_size_mb,
             flush_interval_ms,
+            max_segment_age_secs,
             channel_buffer_size,
         })
     }
@@ -313,6 +335,7 @@ impl AppendOnlyWal {
 
         let max_file_size_bytes = self.max_file_size_mb * 1024 * 1024;
         let flush_duration = Duration::from_millis(self.flush_interval_ms);
+        let max_segment_age = chrono::Duration::seconds(self.max_segment_age_secs as i64);
 
         let mut actor = SegmentWriteActor::new(
             self.wal_type,
@@ -322,6 +345,7 @@ impl AppendOnlyWal {
             file_buf,
             max_file_size_bytes,
             flush_duration,
+            max_segment_age,
             result_tx,
         );
 
@@ -357,6 +381,7 @@ mod tests {
         let max_file_size_mb = 1;
         let flush_interval_ms = 50;
         let channel_buffer = 10;
+        let max_segment_age_secs = 300; // 5 minutes
 
         let wal_writer = AppendOnlyWal::new(
             WalType::Data,
@@ -364,6 +389,7 @@ mod tests {
             max_file_size_mb,
             flush_interval_ms,
             channel_buffer,
+            max_segment_age_secs,
         )
         .await
         .expect("WAL creation failed");
@@ -524,6 +550,7 @@ mod tests {
         let max_file_size_mb = 10;
         let flush_interval_ms = 50;
         let channel_buffer = 10;
+        let max_segment_age_secs = 300; // 5 minutes
 
         let wal_writer = AppendOnlyWal::new(
             WalType::Data,
@@ -531,6 +558,7 @@ mod tests {
             max_file_size_mb,
             flush_interval_ms,
             channel_buffer,
+            max_segment_age_secs,
         )
         .await
         .expect("failed to create wal");
@@ -596,6 +624,7 @@ mod tests {
         let max_file_size_mb = 10;
         let flush_interval_ms = 50;
         let channel_buffer = 10;
+        let max_segment_age_secs = 300; // 5 minutes
 
         let wal_writer = AppendOnlyWal::new(
             WalType::Data,
@@ -603,6 +632,7 @@ mod tests {
             max_file_size_mb,
             flush_interval_ms,
             channel_buffer,
+            max_segment_age_secs,
         )
         .await
         .expect("failed to create wal");
@@ -692,4 +722,84 @@ mod tests {
         //     .expect("writer shouldn't fail")
         //     .expect("WAL shouldn't raise error");
     }
+
+    #[tokio::test]
+    async fn test_wal_time_based_rotation() {
+        let temp_dir = tempdir().unwrap();
+        let base_path = temp_dir.path().to_path_buf();
+        let max_file_size_mb = 10; // Large enough to not trigger size-based rotation
+        let flush_interval_ms = 50;
+        let channel_buffer = 10;
+        let max_segment_age_secs = 2; // Very short for testing
+
+        let wal_writer = AppendOnlyWal::new(
+            WalType::Data,
+            base_path.clone(),
+            max_file_size_mb,
+            flush_interval_ms,
+            channel_buffer,
+            max_segment_age_secs,
+        )
+        .await
+        .expect("failed to create wal");
+
+        let (wal_tx, wal_rx) = mpsc::channel::<SegmentWriteMessage>(channel_buffer);
+        let (_result_rx, _writer_handle) = wal_writer
+            .streaming_write(ReceiverStream::new(wal_rx))
+            .await
+            .expect("Failed to start WAL service");
+
+        // Send some data to the WAL
+        let id1 = Some(Offset::String(StringOffset::new("msg-001".to_string(), 0)));
+        let data1 = Bytes::from("data before time-based rotation");
+        wal_tx
+            .send(SegmentWriteMessage::WriteData {
+                offset: id1.clone(),
+                data: data1.clone(),
+            })
+            .await
+            .unwrap();
+
+        // Wait for the segment age to exceed the threshold
+        tokio::time::sleep(Duration::from_secs(max_segment_age_secs + 1)).await;
+
+        // Send more data after the time threshold
+        let id2 = Some(Offset::String(StringOffset::new("msg-002".to_string(), 0)));
+        let data2 = Bytes::from("data after time-based rotation");
+        wal_tx
+            .send(SegmentWriteMessage::WriteData {
+                offset: id2.clone(),
+                data: data2.clone(),
+            })
+            .await
+            .unwrap();
+
+        // Allow some time for the rotation to complete
+        tokio::time::sleep(Duration::from_millis(500)).await;
+
+        // Verify the files
+        let mut files: Vec<_> = fs::read_dir(&base_path)
+            .unwrap()
+            .map(|entry| entry.unwrap().path())
+            .filter(|path| path.extension().map_or(false, |ext| ext == "frozen"))
+            .collect();
+        files.sort();
+
+        assert!(
+            files.len() >= 1,
+            "There should be at least one frozen file after time-based rotation"
+        );
+
+        // Verify the content of the rotated file
+        let rotated_file_content = fs::read(&files[0]).unwrap();
+        let mut expected_rotated_content = BytesMut::new();
+        expected_rotated_content.extend_from_slice(&data1.len().to_le_bytes());
+        expected_rotated_content.extend_from_slice(&data1);
+        assert_eq!(
+            rotated_file_content, expected_rotated_content,
+            "Rotated file content mismatch"
+        );
+
+        drop(wal_tx);
+    }
 }
diff --git a/rust/numaflow-core/src/reduce/wal/segment/compactor.rs b/rust/numaflow-core/src/reduce/wal/segment/compactor.rs
index 185fa2fe..98acd3e6 100644
--- a/rust/numaflow-core/src/reduce/wal/segment/compactor.rs
+++ b/rust/numaflow-core/src/reduce/wal/segment/compactor.rs
@@ -12,7 +12,6 @@
 //! #### Unaligned kind
 //! 1. Replay all the GC events and store the max end time for every key combination(map[key] = max end time)
 //! 2. Replay all the data events and only retain the messages with event time > max end time for that key
-//!
 
 use crate::reduce::wal::error::WalResult;
 use crate::reduce::wal::segment::GcEventEntry;
@@ -28,12 +27,14 @@ use std::collections::HashMap;
 use std::path::PathBuf;
 use std::time::{Duration, UNIX_EPOCH};
 use tokio::sync::mpsc;
+use tokio::sync::mpsc::Sender;
 use tokio::task::JoinHandle;
 use tokio_stream::{StreamExt, wrappers::ReceiverStream};
 use tokio_util::sync::CancellationToken;
-use tracing::info;
+use tracing::{debug, info};
 
 /// WALs can represent two Kinds of Windows and data is different for each Kind.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub(crate) enum WindowKind {
     /// Aligned represents Fixed and Sliding Windows.
     Aligned,
@@ -50,10 +51,12 @@ pub(crate) struct Compactor {
     segment_wal: ReplayWal,
     /// Compaction WAL for Read Only
     compaction_ro_wal: ReplayWal,
-    /// New Compaction WAL for writing compacted data
-    compaction_ao_wal: AppendOnlyWal,
+    /// Compaction Append WAL tx for writing compacted data
+    compaction_ao_tx: Sender<SegmentWriteMessage>,
     /// Kind of Window, Aligned or Unaligned
     kind: WindowKind,
+    /// join handle for the compaction writer task
+    writer_task_handle: JoinHandle<WalResult<()>>,
 }
 
 const WAL_KEY_SEPERATOR: &str = ":";
@@ -66,6 +69,7 @@ impl Compactor {
         max_file_size_mb: u64,
         flush_interval_ms: u64,
         channel_buffer_size: usize,
+        max_segment_age_secs: u64,
     ) -> WalResult<Self> {
         let segment_wal = ReplayWal::new(WalType::Data, path.clone());
         let gc_wal = ReplayWal::new(WalType::Gc, path.clone());
@@ -76,52 +80,69 @@ impl Compactor {
             max_file_size_mb,
             flush_interval_ms,
             channel_buffer_size,
+            max_segment_age_secs,
         )
         .await?;
 
+        let (compaction_ao_tx, compaction_ao_rx) = mpsc::channel(500);
+        let (_, handle) = compaction_ao_wal
+            .streaming_write(ReceiverStream::new(compaction_ao_rx))
+            .await?;
+
         Ok(Self {
             gc_wal,
             segment_wal,
             compaction_ro_wal,
-            compaction_ao_wal,
+            compaction_ao_tx,
             kind,
+            writer_task_handle: handle,
         })
     }
 
-    /// Compact and replay the compacted data.
-    pub(crate) async fn compact_with_replay(
-        self,
-        tx: mpsc::Sender<Bytes>,
-    ) -> WalResult<JoinHandle<WalResult<()>>> {
-        Ok(tokio::spawn(async move { self.compact(Some(tx)).await }))
-    }
-
-    /// Starts the compaction process every `interval_ms` milliseconds.
-    pub(crate) async fn start_compaction(
+    /// Starts the compaction process with replay and periodic execution.
+    ///
+    /// # Parameters
+    /// * `replay_tx` - Channel to replay compacted data. Compaction runs once and replays data first.
+    /// * `interval` - Interval for periodic compaction. Compaction runs periodically after initial replay.
+    /// * `cln_token` - Cancellation token for periodic compaction.
+    pub(crate) async fn start_compaction_with_replay(
         self,
-        duration: Duration,
+        replay_tx: Sender<Bytes>,
+        interval: Duration,
         cln_token: CancellationToken,
     ) -> WalResult<JoinHandle<WalResult<()>>> {
-        let mut tick = tokio::time::interval(duration);
-        let result: JoinHandle<WalResult<()>> = tokio::spawn(async move {
+        Ok(tokio::spawn(async move {
+            // First do a one-time compaction with replay
+            self.compact(Some(replay_tx)).await?;
+
+            // Then start periodic compaction
+            let mut tick = tokio::time::interval(interval);
             loop {
                 tokio::select! {
-                _ = tick.tick() => {
+                    _ = tick.tick() => {
                         self.compact(None).await?;
-                     }
-                _ = cln_token.cancelled() => {
+                    }
+                    _ = cln_token.cancelled() => {
                         break;
                     }
                 }
             }
-            Ok(())
-        });
-        Ok(result)
+
+            info!("Cancellation token received, stopping compaction task...");
+            // drop the compactor append wal tx to signal the writer task to shut down
+            drop(self.compaction_ao_tx);
+
+            // Wait for the writer task to complete and return the result
+            let result = self.writer_task_handle.await.expect("task failed");
+            info!("Compaction task completed");
+
+            result
+        }))
     }
 
     /// Compact first needs to get all the GC files and build a compaction map. This map will have
     /// the oldest data before which all can be deleted.
-    pub(crate) async fn compact(&self, replay_tx: Option<mpsc::Sender<Bytes>>) -> WalResult<()> {
+    async fn compact(&self, replay_tx: Option<Sender<Bytes>>) -> WalResult<()> {
         match self.kind {
             WindowKind::Aligned => self.compact_aligned(replay_tx.clone()).await?,
             WindowKind::Unaligned => self.compact_unaligned(replay_tx).await?,
@@ -143,6 +164,8 @@ impl Compactor {
         // Get the oldest time and scanned GC files
         let (oldest_time, gc_files) = self.build_aligned_compaction().await?;
 
+        debug!(oldest_time = ?oldest_time.timestamp_millis(), "Event time till which the data has been processed");
+
         let compact = AlignedCompaction(oldest_time);
 
         // Compact the compaction_ro_wal
@@ -155,7 +178,7 @@ impl Compactor {
 
         // Delete the GC files
         for gc_file in gc_files {
-            info!(gc_file = %gc_file.display(), "removing segment file");
+            debug!(gc_file = %gc_file.display(), "removing segment file");
             tokio::fs::remove_file(gc_file).await?;
         }
 
@@ -177,6 +200,8 @@ impl Compactor {
         // Get the oldest time map and scanned GC files
         let (oldest_time_map, gc_files) = self.build_unaligned_compaction().await?;
 
+        info!(oldest_time_map = ?oldest_time_map, "Event time map till which the data has been processed");
+
         let compact = UnalignedCompaction(oldest_time_map);
 
         // Compact the compaction_ro_wal
@@ -208,13 +233,7 @@ impl Compactor {
         // Get a streaming reader for the WAL
         let (mut rx, handle) = wal.clone().streaming_read()?;
 
-        let (wal_tx, wal_rx) = mpsc::channel(100);
-        let (_result_rx, writer_handle) = self
-            .compaction_ao_wal
-            .clone()
-            .streaming_write(ReceiverStream::new(wal_rx))
-            .await
-            .map_err(|e| format!("Failed to start compaction WAL writer: {}", e))?;
+        let wal_tx = self.compaction_ao_tx.clone();
 
         // Process each WAL entry
         while let Some(entry) = rx.next().await {
@@ -247,9 +266,11 @@ impl Compactor {
                         }
                     }
                 }
+
                 SegmentEntry::DataFooter { .. } => {
                     unimplemented!()
                 }
+
                 SegmentEntry::CmdFileSwitch { filename } => {
                     // Send rotate message after processing each file
                     wal_tx
@@ -266,7 +287,7 @@ impl Compactor {
                         )
                     })?;
 
-                    info!(filename = %filename.display(), "removing segment file");
+                    debug!(filename = %filename.display(), "removing segment file");
                 }
             }
         }
@@ -279,11 +300,6 @@ impl Compactor {
         // Drop the sender to close the channel
         drop(wal_tx);
 
-        // Wait for the writer to complete
-        writer_handle
-            .await
-            .map_err(|e| format!("Compaction writer failed: {}", e))??;
-
         Ok(())
     }
 
@@ -306,6 +322,7 @@ impl Compactor {
                     // Decode the GC event.
                     let gc: GcEvent = prost::Message::decode(data)
                         .map_err(|e| format!("Failed to decode GC event: {e}"))?;
+                    println!("GC event: {:?}", gc);
                     let gc: GcEventEntry = gc.into();
 
                     // Update the oldest time if the current GC event's end time is newer.
@@ -413,7 +430,9 @@ impl ShouldRetain for AlignedCompaction {
             .expect("Failed to extract event time from message");
 
         // Retain the message if its event time is greater than the oldest time
-        Ok(event_time > self.0)
+        // we should only compact the messages with event time strictly less than
+        // the oldest time.
+        Ok(event_time >= self.0)
     }
 }
 
@@ -445,7 +464,9 @@ impl ShouldRetain for UnalignedCompaction {
         // Check if the key exists in the map
         if let Some(oldest_time) = self.0.get(&key) {
             // Retain the message if its event time is greater than the oldest time for this key
-            return Ok(event_time > *oldest_time);
+            // we should only compact the messages with event time strictly less than
+            // the oldest time.
+            return Ok(event_time >= *oldest_time);
         }
 
         // If the key is not in the map, retain the message
@@ -461,7 +482,6 @@ mod tests {
     use crate::shared::grpc::prost_timestamp_from_utc;
     use bytes::Bytes;
     use chrono::TimeZone;
-    use prost_types;
     use std::fs;
     use std::sync::Arc;
     use tempfile::tempdir;
@@ -486,6 +506,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             1000, // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -522,6 +543,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -579,6 +601,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             1000, // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -594,7 +617,6 @@ mod tests {
                     nanos: 0,
                 }),
                 keys: vec!["key1".to_string(), "key2".to_string()],
-                ..Default::default()
             },
             GcEvent {
                 start_time: Some(prost_types::Timestamp {
@@ -606,7 +628,6 @@ mod tests {
                     nanos: 0,
                 }),
                 keys: vec!["key1".to_string(), "key2".to_string()],
-                ..Default::default()
             },
             GcEvent {
                 start_time: Some(prost_types::Timestamp {
@@ -618,7 +639,6 @@ mod tests {
                     nanos: 0,
                 }),
                 keys: vec!["key3".to_string(), "key4".to_string()],
-                ..Default::default()
             },
         ];
 
@@ -629,6 +649,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -692,6 +713,7 @@ mod tests {
             1,    // 1MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // max_segment_age_secs
         )
         .await
         .unwrap();
@@ -713,7 +735,7 @@ mod tests {
             keys: vec![],
         };
 
-        let (tx, rx) = tokio::sync::mpsc::channel(10);
+        let (tx, rx) = mpsc::channel(10);
         let (_offset_stream, handle) = gc_wal
             .streaming_write(ReceiverStream::new(rx))
             .await
@@ -743,6 +765,7 @@ mod tests {
             1,    // 1MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // max_segment_age_secs
         )
         .await
         .unwrap();
@@ -756,10 +779,10 @@ mod tests {
 
         let write_result_cnt = tokio::spawn(async move {
             let mut counter = 0;
-            while let Some(_) = offset_stream.next().await {
+            while offset_stream.next().await.is_some() {
                 counter += 1;
             }
-            return counter;
+            counter
         });
 
         let start_time = Utc.with_ymd_and_hms(2025, 4, 1, 1, 0, 0).unwrap();
@@ -805,21 +828,25 @@ mod tests {
             1,    // 1MB
             1000, // 1s flush interval
             500,  // channel buffer
+            300,  // max_segment_age_secs
         )
         .await
         .unwrap();
 
         compactor.compact(None).await.unwrap();
 
+        drop(compactor.compaction_ao_tx);
+        compactor.writer_task_handle.await.unwrap().unwrap();
+
         // Verify compacted data
         let compaction_wal = ReplayWal::new(WalType::Compact, test_path);
         let (mut rx, handle) = compaction_wal.streaming_read().unwrap();
+        let mut replayed_event_times = vec![];
 
         let mut remaining_message_count = 0;
         while let Some(entry) = rx.next().await {
             if let SegmentEntry::DataEntry { data, .. } = entry {
-                let msg: numaflow_pb::objects::isb::ReadMessage =
-                    prost::Message::decode(data).unwrap();
+                let msg: isb::ReadMessage = prost::Message::decode(data).unwrap();
                 if let Some(header) = msg.message.unwrap().header {
                     if let Some(message_info) = header.message_info {
                         let event_time = message_info
@@ -827,9 +854,10 @@ mod tests {
                             .map(utc_from_timestamp)
                             .expect("event time should not be empty");
                         assert!(
-                            event_time > gc_end_2,
-                            "Found message with event_time <= gc_end_2"
+                            event_time >= gc_end_2,
+                            "Found message with event_time < gc_end_2"
                         );
+                        replayed_event_times.push(event_time);
                     }
                 }
                 remaining_message_count += 1;
@@ -838,11 +866,20 @@ mod tests {
         handle.await.unwrap().unwrap();
 
         // Verify the number of remaining messages
-        // Messages with event_time <= gc_end_2 should be removed
+        // with event_time <= gc_end_2 should be removed
         assert_eq!(
-            remaining_message_count, 980,
-            "Expected 980 messages to remain after compaction"
+            remaining_message_count, 981,
+            "Expected 981 messages to remain after compaction"
         );
+
+        // Verify that the event times are in increasing order
+        for i in 1..replayed_event_times.len() {
+            assert_eq!(
+                replayed_event_times[i - 1] + chrono::Duration::seconds(1),
+                replayed_event_times[i],
+                "Event times are not in increasing order"
+            );
+        }
     }
 
     #[tokio::test]
@@ -863,6 +900,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -911,6 +949,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -980,15 +1019,21 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
         // Create a channel to receive replayed messages
         let (replay_tx, mut replay_rx) = mpsc::channel(100);
 
-        // Call compact_with_replay
-        let handle = compactor.compact_with_replay(replay_tx).await?;
+        let cln_token = CancellationToken::new();
 
+        // Call start_compaction with replay
+        let handle = compactor
+            .start_compaction_with_replay(replay_tx, Duration::from_secs(60), cln_token.clone())
+            .await?;
+
+        cln_token.cancel();
         // Wait for compaction to complete
         handle
             .await
@@ -996,8 +1041,10 @@ mod tests {
 
         // Count the number of messages received through the replay channel
         let mut replayed_count = 0;
+        let mut replayed_event_times = Vec::new();
+
         while let Ok(data) = replay_rx.try_recv() {
-            let msg: numaflow_pb::objects::isb::ReadMessage = prost::Message::decode(data)
+            let msg: isb::ReadMessage = prost::Message::decode(data)
                 .map_err(|e| format!("Failed to decode message: {e}"))?;
 
             // Verify that the message has an event time after the GC end time
@@ -1008,6 +1055,9 @@ mod tests {
                         event_time > gc_end,
                         "Found message with event_time <= gc_end"
                     );
+
+                    // Store the event time for later verification
+                    replayed_event_times.push(event_time);
                 }
             }
             replayed_count += 1;
@@ -1019,6 +1069,27 @@ mod tests {
             "Expected only one message to be replayed"
         );
 
+        // Verify that the event time is as expected
+        assert_eq!(
+            replayed_event_times.len(),
+            1,
+            "Expected one event time to be recorded"
+        );
+
+        assert_eq!(
+            replayed_event_times[0], after_time,
+            "Event time of replayed message does not match the original message"
+        );
+
+        // check the order of the replayed event by checking if their event time is increasing by 1
+        for i in 1..replayed_event_times.len() {
+            assert_eq!(
+                replayed_event_times[i - 1] + chrono::Duration::seconds(1),
+                replayed_event_times[i],
+                "Event times are not in increasing order"
+            );
+        }
+
         Ok(())
     }
 
@@ -1040,6 +1111,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -1050,10 +1122,10 @@ mod tests {
 
         let write_result_cnt = tokio::spawn(async move {
             let mut counter = 0;
-            while let Some(_) = gc_wal_write_rx.next().await {
+            while (gc_wal_write_rx.next().await).is_some() {
                 counter += 1;
             }
-            return counter;
+            counter
         });
 
         // GC event for key1:key2 with end time
@@ -1063,7 +1135,6 @@ mod tests {
             start_time: Some(prost_timestamp_from_utc(gc_start_1)),
             end_time: Some(prost_timestamp_from_utc(gc_end_1)),
             keys: vec!["key1".to_string(), "key2".to_string()],
-            ..Default::default()
         };
 
         // GC event for key3:key4 with a different end time
@@ -1073,7 +1144,6 @@ mod tests {
             start_time: Some(prost_timestamp_from_utc(gc_start_2)),
             end_time: Some(prost_timestamp_from_utc(gc_end_2)),
             keys: vec!["key3".to_string(), "key4".to_string()],
-            ..Default::default()
         };
 
         // Write the GC events to the WAL
@@ -1105,6 +1175,7 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
@@ -1195,15 +1266,21 @@ mod tests {
             100,  // max_file_size_mb
             1000, // flush_interval_ms
             100,  // channel_buffer_size
+            300,  // max_segment_age_secs
         )
         .await?;
 
         // Create a channel to receive replayed messages
         let (replay_tx, mut replay_rx) = mpsc::channel(100);
 
-        // Call compact_with_replay
-        let handle = compactor.compact_with_replay(replay_tx).await?;
+        let cln_token = CancellationToken::new();
+
+        // Call start_compaction with replay
+        let handle = compactor
+            .start_compaction_with_replay(replay_tx, Duration::from_secs(60), cln_token.clone())
+            .await?;
 
+        cln_token.cancel();
         // Wait for compaction to complete
         handle
             .await
@@ -1215,7 +1292,7 @@ mod tests {
         let mut key3_key4_count = 0;
 
         while let Ok(data) = replay_rx.try_recv() {
-            let msg: numaflow_pb::objects::isb::ReadMessage = prost::Message::decode(data)
+            let msg: isb::ReadMessage = prost::Message::decode(data)
                 .map_err(|e| format!("Failed to decode message: {e}"))?;
 
             // Verify that the message has an event time after the appropriate GC end time
diff --git a/rust/numaflow-core/src/shared/create_components.rs b/rust/numaflow-core/src/shared/create_components.rs
index a53ec4ee..deaa5ac1 100644
--- a/rust/numaflow-core/src/shared/create_components.rs
+++ b/rust/numaflow-core/src/shared/create_components.rs
@@ -1,33 +1,43 @@
 use std::time::Duration;
 
-use numaflow_pb::clients::map::map_client::MapClient;
-use numaflow_pb::clients::sink::sink_client::SinkClient;
-use numaflow_pb::clients::source::source_client::SourceClient;
-use numaflow_pb::clients::sourcetransformer::source_transform_client::SourceTransformClient;
-use tokio_util::sync::CancellationToken;
-
+use crate::config::components::reduce::ReducerType;
 use crate::config::components::sink::{SinkConfig, SinkType};
 use crate::config::components::source::{SourceConfig, SourceType};
 use crate::config::components::transformer::TransformerConfig;
 use crate::config::get_vertex_replica;
 use crate::config::pipeline::map::{MapMode, MapType, MapVtxConfig};
-use crate::config::pipeline::{DEFAULT_BATCH_MAP_SOCKET, DEFAULT_STREAM_MAP_SOCKET};
+use crate::config::pipeline::watermark::WatermarkConfig;
+use crate::config::pipeline::{
+    DEFAULT_BATCH_MAP_SOCKET, DEFAULT_STREAM_MAP_SOCKET, PipelineConfig,
+};
 use crate::error::Error;
 use crate::mapper::map::MapHandle;
+use crate::reduce::reducer::aligned::user_defined::UserDefinedAlignedReduce;
+use crate::reduce::reducer::aligned::windower::WindowManager;
 use crate::shared::grpc;
-use crate::shared::server_info::{ContainerType, sdk_server_info};
+use crate::shared::server_info::{ContainerType, Protocol, sdk_server_info};
 use crate::sink::serve::ServingStore;
 use crate::sink::{SinkClientType, SinkWriter, SinkWriterBuilder};
 use crate::source::Source;
 use crate::source::generator::new_generator;
 use crate::source::jetstream::new_jetstream_source;
+use crate::source::kafka::new_kafka_source;
 use crate::source::pulsar::new_pulsar_source;
 use crate::source::sqs::new_sqs_source;
 use crate::source::user_defined::new_source;
 use crate::tracker::TrackerHandle;
 use crate::transformer::Transformer;
+use crate::watermark::isb::ISBWatermarkHandle;
 use crate::watermark::source::SourceWatermarkHandle;
 use crate::{config, error, metrics, source};
+use async_nats::jetstream::Context;
+use numaflow_pb::clients::map::map_client::MapClient;
+use numaflow_pb::clients::reduce::reduce_client::ReduceClient;
+use numaflow_pb::clients::sink::sink_client::SinkClient;
+use numaflow_pb::clients::source::source_client::SourceClient;
+use numaflow_pb::clients::sourcetransformer::source_transform_client::SourceTransformClient;
+use numaflow_sqs::sink::SqsSinkBuilder;
+use tokio_util::sync::CancellationToken;
 
 /// Creates a sink writer based on the configuration
 pub(crate) async fn create_sink_writer(
@@ -68,7 +78,7 @@ pub(crate) async fn create_sink_writer(
                 config::get_vertex_name().to_string(),
                 sink_server_info.language,
                 sink_server_info.version,
-                ContainerType::Sourcer.to_string(),
+                ContainerType::Sinker.to_string(),
             );
 
             metrics::global_metrics()
@@ -90,6 +100,25 @@ pub(crate) async fn create_sink_writer(
             )
             .retry_config(primary_sink.retry_config.unwrap_or_default())
         }
+        SinkType::Sqs(sqs_sink_config) => {
+            let sqs_sink = SqsSinkBuilder::new(sqs_sink_config).build().await?;
+            SinkWriterBuilder::new(
+                batch_size,
+                read_timeout,
+                SinkClientType::Sqs(sqs_sink.clone()),
+                tracker_handle,
+            )
+        }
+        SinkType::Kafka(sink_config) => {
+            let sink_config = *sink_config.clone();
+            let kafka_sink = numaflow_kafka::sink::new_sink(sink_config)?;
+            SinkWriterBuilder::new(
+                batch_size,
+                read_timeout,
+                SinkClientType::Kafka(kafka_sink),
+                tracker_handle,
+            )
+        }
     };
 
     if let Some(fb_sink) = fallback_sink {
@@ -116,7 +145,7 @@ pub(crate) async fn create_sink_writer(
                     config::get_vertex_name().to_string(),
                     fb_server_info.language,
                     fb_server_info.version,
-                    ContainerType::Sourcer.to_string(),
+                    ContainerType::FbSinker.to_string(),
                 );
 
                 metrics::global_metrics()
@@ -136,6 +165,21 @@ pub(crate) async fn create_sink_writer(
                     .build()
                     .await?)
             }
+            SinkType::Sqs(sqs_sink_config) => {
+                let sqs_sink = SqsSinkBuilder::new(sqs_sink_config).build().await?;
+                Ok(sink_writer_builder
+                    .fb_sink_client(SinkClientType::Sqs(sqs_sink.clone()))
+                    .build()
+                    .await?)
+            }
+            SinkType::Kafka(sink_config) => {
+                let sink_config = *sink_config.clone();
+                let kafka_sink = numaflow_kafka::sink::new_sink(sink_config)?;
+                Ok(sink_writer_builder
+                    .fb_sink_client(SinkClientType::Kafka(kafka_sink))
+                    .build()
+                    .await?)
+            }
         };
     }
 
@@ -167,7 +211,7 @@ pub(crate) async fn create_transformer(
                 config::get_vertex_name().to_string(),
                 server_info.language,
                 server_info.version,
-                ContainerType::Sourcer.to_string(),
+                ContainerType::SourceTransformer.to_string(),
             );
             metrics::global_metrics()
                 .sdk_info
@@ -206,46 +250,81 @@ pub(crate) async fn create_mapper(
             let server_info =
                 sdk_server_info(config.server_info_path.clone().into(), cln_token.clone()).await?;
 
-            // based on the map mode that is set in the server info, we will override the socket path
-            // so that the clients can connect to the appropriate socket.
-            let config = match server_info.get_map_mode().unwrap_or(MapMode::Unary) {
-                MapMode::Unary => config,
-                MapMode::Batch => {
-                    config.socket_path = DEFAULT_BATCH_MAP_SOCKET.into();
-                    config
-                }
-                MapMode::Stream => {
-                    config.socket_path = DEFAULT_STREAM_MAP_SOCKET.into();
-                    config
-                }
-            };
-
+            // add sdk info metric
             let metric_labels = metrics::sdk_info_labels(
                 config::get_component_type().to_string(),
                 config::get_vertex_name().to_string(),
                 server_info.language.clone(),
                 server_info.version.clone(),
-                ContainerType::Sourcer.to_string(),
+                ContainerType::Mapper.to_string(),
             );
             metrics::global_metrics()
                 .sdk_info
                 .get_or_create(&metric_labels)
                 .set(1);
 
-            let mut map_grpc_client =
-                MapClient::new(grpc::create_rpc_channel(config.socket_path.clone().into()).await?)
+            match server_info.get_protocol() {
+                Protocol::TCP => {
+                    // tcp is only used for multi proc mode in python
+                    let endpoints = server_info.get_http_endpoints();
+
+                    // Bug in tonic, https://github.com/hyperium/tonic/issues/2257 we will enable it
+                    // once it's fixed.
+                    if endpoints.len() > 1 {
+                        return Err(Error::Mapper(
+                            "Multi proc mode is not supported".to_string(),
+                        ));
+                    }
+
+                    let channel = grpc::create_multi_rpc_channel(endpoints).await?;
+
+                    let map_grpc_client = MapClient::new(channel)
+                        .max_encoding_message_size(config.grpc_max_message_size)
+                        .max_decoding_message_size(config.grpc_max_message_size);
+
+                    Ok(MapHandle::new(
+                        server_info.get_map_mode().unwrap_or(MapMode::Unary),
+                        batch_size,
+                        read_timeout,
+                        map_config.concurrency,
+                        map_grpc_client.clone(),
+                        tracker_handle,
+                    )
+                    .await?)
+                }
+                Protocol::UDS => {
+                    // based on the map mode that is set in the server info, we will override the socket path
+                    // so that the clients can connect to the appropriate socket.
+                    let config = match server_info.get_map_mode().unwrap_or(MapMode::Unary) {
+                        MapMode::Unary => config,
+                        MapMode::Batch => {
+                            config.socket_path = DEFAULT_BATCH_MAP_SOCKET.into();
+                            config
+                        }
+                        MapMode::Stream => {
+                            config.socket_path = DEFAULT_STREAM_MAP_SOCKET.into();
+                            config
+                        }
+                    };
+
+                    let mut map_grpc_client = MapClient::new(
+                        grpc::create_rpc_channel(config.socket_path.clone().into()).await?,
+                    )
                     .max_encoding_message_size(config.grpc_max_message_size)
                     .max_decoding_message_size(config.grpc_max_message_size);
-            grpc::wait_until_mapper_ready(&cln_token, &mut map_grpc_client).await?;
-            Ok(MapHandle::new(
-                server_info.get_map_mode().unwrap_or(MapMode::Unary),
-                batch_size,
-                read_timeout,
-                map_config.concurrency,
-                map_grpc_client.clone(),
-                tracker_handle,
-            )
-            .await?)
+
+                    grpc::wait_until_mapper_ready(&cln_token, &mut map_grpc_client).await?;
+                    Ok(MapHandle::new(
+                        server_info.get_map_mode().unwrap_or(MapMode::Unary),
+                        batch_size,
+                        read_timeout,
+                        map_config.concurrency,
+                        map_grpc_client.clone(),
+                        tracker_handle,
+                    )
+                    .await?)
+                }
+            }
         }
         MapType::Builtin(_) => Err(Error::Mapper("Builtin mapper is not supported".to_string())),
     }
@@ -339,7 +418,7 @@ pub async fn create_source(
             .await?;
             Ok(Source::new(
                 batch_size,
-                source::SourceType::SQS(sqs),
+                source::SourceType::Sqs(sqs),
                 tracker_handle,
                 source_config.read_ahead,
                 transformer,
@@ -358,6 +437,41 @@ pub async fn create_source(
                 watermark_handle,
             ))
         }
+        SourceType::Kafka(kafka_config) => {
+            let config = *kafka_config.clone();
+            let kafka = new_kafka_source(config, batch_size, read_timeout).await?;
+            Ok(Source::new(
+                batch_size,
+                source::SourceType::Kafka(kafka),
+                tracker_handle,
+                source_config.read_ahead,
+                transformer,
+                watermark_handle,
+            ))
+        }
+    }
+}
+
+/// Creates a user-defined aligned reducer client
+pub(crate) async fn create_aligned_reducer(
+    reducer_config: config::components::reduce::ReducerConfig,
+) -> crate::Result<UserDefinedAlignedReduce> {
+    // TODO: add server-info metric and check compatibility
+    match reducer_config.reducer_type {
+        ReducerType::UserDefined(config) => {
+            // Create gRPC channel
+            let channel = grpc::create_rpc_channel(config.socket_path.into()).await?;
+
+            // Create client
+            let client = UserDefinedAlignedReduce::new(
+                ReduceClient::new(channel)
+                    .max_encoding_message_size(config.grpc_max_message_size)
+                    .max_decoding_message_size(config.grpc_max_message_size),
+            )
+            .await;
+
+            Ok(client)
+        }
     }
 }
 
@@ -520,3 +634,29 @@ mod tests {
         transformer_server_handle.await.unwrap();
     }
 }
+
+/// Creates an ISBWatermarkHandle if watermark is enabled in the configuration
+pub async fn create_edge_watermark_handle(
+    config: &PipelineConfig,
+    js_context: &Context,
+    cln_token: &CancellationToken,
+    window_manager: Option<WindowManager>,
+) -> error::Result<Option<ISBWatermarkHandle>> {
+    match &config.watermark_config {
+        Some(WatermarkConfig::Edge(edge_config)) => {
+            let handle = ISBWatermarkHandle::new(
+                config.vertex_name,
+                config.replica,
+                config.read_timeout,
+                js_context.clone(),
+                edge_config,
+                &config.to_vertex_config,
+                cln_token.clone(),
+                window_manager,
+            )
+            .await?;
+            Ok(Some(handle))
+        }
+        _ => Ok(None),
+    }
+}
diff --git a/rust/numaflow-core/src/shared/forward.rs b/rust/numaflow-core/src/shared/forward.rs
index 1eaa716d..2a98a679 100644
--- a/rust/numaflow-core/src/shared/forward.rs
+++ b/rust/numaflow-core/src/shared/forward.rs
@@ -3,22 +3,30 @@ use std::sync::Arc;
 
 use numaflow_models::models::ForwardConditions;
 
-/// Checks if the message should to written to downstream vertex based the conditions
-/// and message tags. If not tags are provided by there are edge conditions present, we will
-/// still forward to all vertices.
+/// Checks if the message should be written to downstream vertex based on the conditions
+/// and message tags. If no tags are provided, we treat it as empty tags and still perform
+/// the condition check.
 pub(crate) fn should_forward(
     tags: Option<Arc<[String]>>,
     conditions: Option<Box<ForwardConditions>>,
 ) -> bool {
-    conditions.is_none_or(|conditions| {
-        conditions.tags.operator.as_ref().is_none_or(|operator| {
-            tags.as_ref().is_none_or(|tags| {
-                !conditions.tags.values.is_empty()
-                    && check_operator_condition(operator, &conditions.tags.values, tags)
-            })
-        })
-    })
+    // we should forward the message to downstream vertex if there are no edge conditions
+    let Some(conditions) = conditions else {
+        return true;
+    };
+
+    // Return true if there are no tags in the edge condition
+    if conditions.tags.values.is_empty() {
+        return true;
+    }
+
+    // Treat missing tags as empty and check the condition
+    let tags = tags.unwrap_or_else(|| Arc::from(vec![]));
+    // Default operator is "or", if not specified
+    let operator = conditions.tags.operator.as_deref().unwrap_or("or");
+    check_operator_condition(operator, &conditions.tags.values, &tags)
 }
+
 /// Determine the partition to write the message to by hashing the message id.
 pub(crate) fn determine_partition(
     message_id: String,
@@ -72,13 +80,6 @@ mod tests {
         assert!(result);
     }
 
-    #[tokio::test]
-    async fn test_evaluate_write_condition_no_tags() {
-        let conditions = ForwardConditions::new(TagConditions::new(vec!["tag1".to_string()]));
-        let result = should_forward(None, Some(Box::new(conditions)));
-        assert!(result);
-    }
-
     #[tokio::test]
     async fn test_evaluate_write_condition_and_operator() {
         let mut tag_conditions = TagConditions::new(vec!["tag1".to_string(), "tag2".to_string()]);
@@ -108,4 +109,61 @@ mod tests {
         let result = should_forward(tags, Some(Box::new(conditions)));
         assert!(result);
     }
+
+    #[tokio::test]
+    async fn test_empty_tags_with_and_operator() {
+        let mut tag_conditions = TagConditions::new(vec!["tag1".to_string(), "tag2".to_string()]);
+        tag_conditions.operator = Some("and".to_string());
+        let conditions = ForwardConditions::new(tag_conditions);
+
+        // Empty tags array (explicit empty)
+        let tags = Some(Arc::from(Vec::<String>::new()));
+        let result = should_forward(tags, Some(Box::new(conditions.clone())));
+        assert!(!result, "AND condition should fail with empty tags");
+
+        // None tags
+        let result = should_forward(None, Some(Box::new(conditions)));
+        assert!(!result, "AND condition should fail with None tags");
+    }
+
+    #[tokio::test]
+    async fn test_empty_tags_with_or_operator() {
+        let mut tag_conditions = TagConditions::new(vec!["tag1".to_string(), "tag2".to_string()]);
+        tag_conditions.operator = Some("or".to_string());
+        let conditions = ForwardConditions::new(tag_conditions);
+
+        // Empty tags array (explicit empty)
+        let tags = Some(Arc::from(Vec::<String>::new()));
+        let result = should_forward(tags, Some(Box::new(conditions.clone())));
+        assert!(!result, "OR condition should fail with empty tags");
+
+        // None tags
+        let result = should_forward(None, Some(Box::new(conditions)));
+        assert!(!result, "OR condition should fail with None tags");
+    }
+
+    #[tokio::test]
+    async fn test_empty_tags_with_not_operator() {
+        let mut tag_conditions = TagConditions::new(vec!["tag1".to_string(), "tag2".to_string()]);
+        tag_conditions.operator = Some("not".to_string());
+        let conditions = ForwardConditions::new(tag_conditions);
+
+        // Empty tags array (explicit empty)
+        let tags = Some(Arc::from(Vec::<String>::new()));
+        let result = should_forward(tags, Some(Box::new(conditions.clone())));
+        assert!(result, "NOT condition should pass with empty tags");
+
+        // None tags
+        let result = should_forward(None, Some(Box::new(conditions)));
+        assert!(result, "NOT condition should pass with None tags");
+    }
+
+    #[tokio::test]
+    async fn test_default_operator() {
+        let tag_conditions = TagConditions::new(vec!["tag1".to_string(), "tag2".to_string()]);
+        let conditions = ForwardConditions::new(tag_conditions);
+        let tags = Some(Arc::from(vec!["tag1".to_string(), "tag2".to_string()]));
+        let result = should_forward(tags, Some(Box::new(conditions)));
+        assert!(result);
+    }
 }
diff --git a/rust/numaflow-core/src/shared/grpc.rs b/rust/numaflow-core/src/shared/grpc.rs
index a2c83f63..9905e6a1 100644
--- a/rust/numaflow-core/src/shared/grpc.rs
+++ b/rust/numaflow-core/src/shared/grpc.rs
@@ -119,7 +119,7 @@ pub(crate) async fn create_rpc_channel(socket_path: PathBuf) -> error::Result<Ch
         async || match connect_with_uds(socket_path.clone()).await {
             Ok(channel) => Ok(channel),
             Err(e) => {
-                warn!(?e, ?socket_path, "Failed to connect to UDS socket");
+                warn!(error = ?e, ?socket_path, "Failed to connect to UDS socket");
                 Err(Error::Connection(format!(
                     "Failed to connect {socket_path:?}: {:?}",
                     e
@@ -152,3 +152,19 @@ pub(crate) async fn connect_with_uds(uds_path: PathBuf) -> error::Result<Channel
 pub(crate) fn utc_from_timestamp(t: Timestamp) -> DateTime<Utc> {
     DateTime::from_timestamp(t.seconds, t.nanos as u32).unwrap_or(Utc.timestamp_nanos(-1))
 }
+
+/// Creates a load-balanced channel across multiple HTTP endpoints.
+pub(crate) async fn create_multi_rpc_channel(endpoints: Vec<String>) -> error::Result<Channel> {
+    if endpoints.is_empty() {
+        return Err(Error::Connection("No endpoints provided".to_string()));
+    }
+
+    let endpoints: Vec<Endpoint> = endpoints
+        .into_iter()
+        .map(|a| Channel::from_shared(a).expect("valid address"))
+        .collect();
+
+    let channel = Channel::balance_list(endpoints.into_iter());
+
+    Ok(channel)
+}
diff --git a/rust/numaflow-core/src/shared/server_info.rs b/rust/numaflow-core/src/shared/server_info.rs
index 53cbef6e..c200d5c2 100644
--- a/rust/numaflow-core/src/shared/server_info.rs
+++ b/rust/numaflow-core/src/shared/server_info.rs
@@ -20,6 +20,7 @@ use crate::shared::server_info::version::SdkConstraints;
 // Equivalent to U+005C__END__.
 const END: &str = "U+005C__END__";
 const MAP_MODE_KEY: &str = "MAP_MODE";
+const HTTP_ENDPOINTS_KEY: &str = "MULTIPROC_ENDPOINTS";
 
 #[derive(Debug, Eq, PartialEq, Clone, Hash)]
 pub enum ContainerType {
@@ -90,6 +91,14 @@ pub(crate) struct ServerInfo {
     pub(crate) metadata: Option<HashMap<String, String>>, // Metadata is optional
 }
 
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub(crate) enum Protocol {
+    #[allow(clippy::upper_case_acronyms)]
+    TCP,
+    #[allow(clippy::upper_case_acronyms)]
+    UDS,
+}
+
 impl ServerInfo {
     pub(crate) fn get_map_mode(&self) -> Option<MapMode> {
         if let Some(metadata) = &self.metadata {
@@ -99,6 +108,24 @@ impl ServerInfo {
         }
         None
     }
+
+    // get_http_endpoints returns the list of http endpoints for multi proc mode
+    // from the metadata.
+    pub(crate) fn get_http_endpoints(&self) -> Vec<String> {
+        if let Some(metadata) = &self.metadata {
+            if let Some(endpoints) = metadata.get(HTTP_ENDPOINTS_KEY) {
+                return endpoints.split(',').map(|s| s.to_string()).collect();
+            }
+        }
+        vec![]
+    }
+
+    pub(crate) fn get_protocol(&self) -> Protocol {
+        match self.protocol.as_str() {
+            "tcp" => Protocol::TCP,
+            _ => Protocol::UDS,
+        }
+    }
 }
 
 /// sdk_server_info waits until the server info file is ready and check whether the
@@ -236,8 +263,8 @@ fn check_sdk_compatibility(
     } else {
         // Language not found in the supported SDK versions
         warn!(
-            "SDK version constraint not found for language: {}, container type: {}",
-            sdk_language, container_type
+            %sdk_language,
+            %container_type, "SDK version constraint not found for language and container type"
         );
 
         // Return error indicating the language
@@ -351,7 +378,12 @@ async fn read_server_info(
     // Infinite loop to keep checking until the file is ready
     loop {
         if cln_token.is_cancelled() {
-            return Err(Error::ServerInfo("Operation cancelled".to_string()));
+            return Err(Error::ServerInfo(format!(
+                "Server info file {:?} is not ready. \
+                This indicates that the server has not started. \
+                For more details https://numaflow.numaproj.io/user-guide/FAQ/#4-i-see-server-info-file-not-ready-log-in-the-numa-container-what-does-this-mean",
+                file_path
+            )));
         }
 
         // Check if the file exists and has content
@@ -377,12 +409,11 @@ async fn read_server_info(
                     // If the file ends with the END marker, trim it and break out of the loop
                     contents = data.trim_end_matches(END).to_string();
                     break;
-                } else {
-                    warn!("Server info file is incomplete, EOF is missing...");
                 }
+                warn!("Server info file is incomplete, EOF is missing...");
             }
             Err(e) => {
-                warn!("Failed to read file: {}", e);
+                warn!(error = ?e, ?file_path, "Failed to read server info file");
             }
         }
 
@@ -390,9 +421,10 @@ async fn read_server_info(
         retry += 1;
         if retry >= 10 {
             // Return an error if the retry limit is reached
-            return Err(Error::ServerInfo(
-                "server-info reading retry exceeded".to_string(),
-            ));
+            return Err(Error::ServerInfo(format!(
+                "server-info reading retry exceeded for file: {:?}",
+                file_path
+            )));
         }
 
         sleep(Duration::from_millis(100)).await; // Sleep before retrying
diff --git a/rust/numaflow-core/src/sink.rs b/rust/numaflow-core/src/sink.rs
index e706e954..a4188483 100644
--- a/rust/numaflow-core/src/sink.rs
+++ b/rust/numaflow-core/src/sink.rs
@@ -3,10 +3,14 @@
 //!
 //! [Sink]: https://numaflow.numaproj.io/user-guide/sinks/overview/
 
+use numaflow_kafka::sink::KafkaSink;
 use numaflow_pb::clients::serving::serving_store_client::ServingStoreClient;
 use numaflow_pb::clients::sink::Status::{Failure, Fallback, Serve, Success};
 use numaflow_pb::clients::sink::sink_client::SinkClient;
 use numaflow_pb::clients::sink::sink_response;
+use numaflow_sqs::sink::SqsSink;
+use rand::rngs::StdRng;
+use rand::{Rng, SeedableRng};
 use serving::{DEFAULT_ID_HEADER, DEFAULT_POD_HASH_KEY};
 use std::collections::HashMap;
 use std::time::Duration;
@@ -24,12 +28,13 @@ use user_defined::UserDefinedSink;
 
 use crate::Result;
 use crate::config::components::sink::{OnFailureStrategy, RetryConfig};
+use crate::config::pipeline::VERTEX_TYPE_SINK;
 use crate::config::{get_vertex_name, is_mono_vertex};
 use crate::error::Error;
 use crate::message::Message;
 use crate::metrics::{
-    monovertex_metrics, mvtx_forward_metric_labels, pipeline_forward_metric_labels,
-    pipeline_metrics,
+    PIPELINE_PARTITION_NAME_LABEL, monovertex_metrics, mvtx_forward_metric_labels,
+    pipeline_drop_metric_labels, pipeline_metric_labels, pipeline_metrics,
 };
 use crate::sink::serve::{ServingStore, StoreEntry};
 use crate::tracker::TrackerHandle;
@@ -48,6 +53,9 @@ mod log;
 /// to write to the serving store.
 pub mod serve;
 
+mod sqs;
+
+mod kafka;
 /// [User-Defined Sink] extends Numaflow to add custom sources supported outside the builtins.
 ///
 /// [User-Defined Sink]: https://numaflow.numaproj.io/user-guide/sinks/user-defined-sinks/
@@ -103,6 +111,8 @@ pub(crate) enum SinkClientType {
     Blackhole,
     Serve,
     UserDefined(SinkClient<Channel>),
+    Sqs(SqsSink),
+    Kafka(KafkaSink),
 }
 
 /// User defined clients which will be used for doing sidecar health checks.
@@ -295,6 +305,18 @@ impl SinkWriterBuilder {
                     actor.run().await;
                 });
             }
+            SinkClientType::Sqs(sqs_sink) => {
+                tokio::spawn(async {
+                    let actor = SinkActor::new(receiver, sqs_sink);
+                    actor.run().await;
+                });
+            }
+            SinkClientType::Kafka(kafka_sink) => {
+                tokio::spawn(async {
+                    let actor = SinkActor::new(receiver, kafka_sink);
+                    actor.run().await;
+                });
+            }
         };
 
         // start fallback sinks
@@ -330,6 +352,18 @@ impl SinkWriterBuilder {
                         actor.run().await;
                     });
                 }
+                SinkClientType::Sqs(sqs_sink) => {
+                    tokio::spawn(async {
+                        let actor = SinkActor::new(fb_receiver, sqs_sink);
+                        actor.run().await;
+                    });
+                }
+                SinkClientType::Kafka(kafka_sink) => {
+                    tokio::spawn(async {
+                        let actor = SinkActor::new(fb_receiver, kafka_sink);
+                        actor.run().await;
+                    });
+                }
             };
             Some(fb_sender)
         } else {
@@ -405,9 +439,6 @@ impl SinkWriter {
 
                 pin!(chunk_stream);
 
-                let mut processed_msgs_count: usize = 0;
-                let mut last_logged_at = std::time::Instant::now();
-
                 loop {
                     let batch = match chunk_stream.next().await {
                         Some(batch) => batch,
@@ -437,15 +468,11 @@ impl SinkWriter {
                         .map(|msg| msg.offset.clone())
                         .collect::<Vec<_>>();
 
-                    let total_msgs = batch.len();
                     // filter out the messages which needs to be dropped
                     let batch = batch
                         .into_iter()
                         .filter(|msg| !msg.dropped())
                         .collect::<Vec<_>>();
-
-                    let sink_start = time::Instant::now();
-                    let total_valid_msgs = batch.len();
                     match self.write(batch.clone(), cln_token.clone()).await {
                         Ok(_) => {
                             for offset in offsets {
@@ -469,37 +496,6 @@ impl SinkWriter {
                             self.shutting_down = true;
                         }
                     }
-
-                    // publish sink metrics
-                    if is_mono_vertex() {
-                        monovertex_metrics()
-                            .sink
-                            .time
-                            .get_or_create(mvtx_forward_metric_labels())
-                            .observe(sink_start.elapsed().as_micros() as f64);
-                    } else {
-                        pipeline_metrics()
-                            .forwarder
-                            .write_time
-                            .get_or_create(pipeline_forward_metric_labels("Sink"))
-                            .observe(sink_start.elapsed().as_micros() as f64);
-                        pipeline_metrics()
-                            .forwarder
-                            .dropped_total
-                            .get_or_create(pipeline_forward_metric_labels("Sink"))
-                            .inc_by((total_msgs - total_valid_msgs) as u64);
-                    }
-
-                    processed_msgs_count += total_msgs;
-                    if last_logged_at.elapsed().as_millis() >= 1000 {
-                        info!(
-                            "Processed {} messages at {:?}",
-                            processed_msgs_count,
-                            time::Instant::now()
-                        );
-                        processed_msgs_count = 0;
-                        last_logged_at = std::time::Instant::now();
-                    }
                 }
                 self.final_result.clone()
             }
@@ -517,8 +513,10 @@ impl SinkWriter {
             return Ok(());
         }
 
+        let write_start_time = tokio::time::Instant::now();
         let total_msgs = messages.len();
-        let mut attempts = 0;
+        let mut retry_attempts = 0;
+        let total_msgs_bytes: usize = messages.iter().map(|msg| msg.value.len()).sum();
         let mut error_map = HashMap::new();
         let mut fallback_msgs = Vec::new();
         let mut serving_msgs = Vec::new();
@@ -530,27 +528,41 @@ impl SinkWriter {
         // only breaks out of this loop based on the retry strategy unless all the messages have been written to sink
         // successfully.
         let retry_config = &self.retry_config.clone();
+        // Initialize a `StdRng` instance which implements std::marker::Send
+        let mut rng = StdRng::from_entropy();
 
         loop {
-            while attempts < retry_config.sink_max_retry_attempts {
+            while retry_attempts <= retry_config.sink_max_retry_attempts {
                 let status = self
                     .write_to_sink_once(
                         &mut error_map,
                         &mut fallback_msgs,
                         &mut serving_msgs,
                         &mut messages_to_send,
-                        retry_config,
                     )
                     .await;
                 match status {
                     Ok(true) => break,
                     Ok(false) => {
-                        attempts += 1;
+                        // Increment retry attempt only if we will retry again
+                        // otherwise we will break out of the loop
+                        // sleep for the calculated delay only if we are retrying
+                        if retry_attempts >= retry_config.sink_max_retry_attempts {
+                            break;
+                        }
+                        retry_attempts += 1;
+                        write_errors_total += error_map.len();
                         warn!(
-                            "Retry attempt {} due to retryable error. Errors: {:?}",
-                            attempts, error_map
+                            retry_attempt = retry_attempts,
+                            ?error_map,
+                            "Retrying due to retryable error."
                         );
-                        write_errors_total += error_map.len();
+                        let delay = Self::calculate_exponential_delay(
+                            retry_config,
+                            retry_attempts,
+                            &mut rng,
+                        );
+                        sleep(Duration::from_millis(delay as u64)).await;
                     }
                     Err(e) => Err(e)?,
                 }
@@ -568,7 +580,7 @@ impl SinkWriter {
 
             // If after the retries we still have messages to process, handle the post retry failures
             let need_retry = Self::handle_sink_post_retry(
-                &mut attempts,
+                &mut retry_attempts,
                 &mut error_map,
                 &mut fallback_msgs,
                 &mut messages_to_send,
@@ -578,9 +590,9 @@ impl SinkWriter {
             match need_retry {
                 // if we are done with the messages, break the loop
                 Ok(false) => break,
-                // if we need to retry, reset the attempts and error_map
+                // if we need to retry, reset the retry_attempts and error_map
                 Ok(true) => {
-                    attempts = 0;
+                    retry_attempts = 0;
                     error_map.clear();
                 }
                 Err(e) => Err(e)?,
@@ -588,37 +600,64 @@ impl SinkWriter {
         }
 
         let fb_msgs_total = fallback_msgs.len();
+        let fb_msgs_bytes_total: usize = fallback_msgs.iter().map(|msg| msg.value.len()).sum();
         // If there are fallback messages, write them to the fallback sink
         if !fallback_msgs.is_empty() {
             let fallback_sink_start = time::Instant::now();
             self.handle_fallback_messages(fallback_msgs, retry_config)
                 .await?;
-            Self::send_fb_sink_metrics(fb_msgs_total, fallback_sink_start);
+            Self::send_fb_sink_metrics(fb_msgs_total, fb_msgs_bytes_total, fallback_sink_start);
         }
 
         let serving_msgs_total = serving_msgs.len();
+        let serving_msgs_bytes_total: usize = serving_msgs.iter().map(|msg| msg.value.len()).sum();
         // If there are serving messages, write them to the serving store
         if !serving_msgs.is_empty() {
             self.handle_serving_messages(serving_msgs).await?;
         }
 
         if is_mono_vertex() {
+            monovertex_metrics()
+                .sink
+                .time
+                .get_or_create(mvtx_forward_metric_labels())
+                .observe(write_start_time.elapsed().as_micros() as f64);
             monovertex_metrics()
                 .sink
                 .write_total
                 .get_or_create(mvtx_forward_metric_labels())
-                .inc_by((total_msgs - fb_msgs_total - serving_msgs_total) as u64);
+                .inc_by((total_msgs - fb_msgs_total) as u64);
             monovertex_metrics()
                 .sink
                 .write_errors_total
                 .get_or_create(mvtx_forward_metric_labels())
                 .inc_by((write_errors_total) as u64);
         } else {
+            let mut labels = pipeline_metric_labels(VERTEX_TYPE_SINK).clone();
+            labels.push((
+                PIPELINE_PARTITION_NAME_LABEL.to_string(),
+                get_vertex_name().to_string(),
+            ));
             pipeline_metrics()
                 .forwarder
                 .write_total
-                .get_or_create(pipeline_forward_metric_labels("Sink"))
-                .inc_by(total_msgs as u64);
+                .get_or_create(&labels)
+                .inc_by((total_msgs - fb_msgs_total - serving_msgs_total) as u64);
+            pipeline_metrics()
+                .forwarder
+                .write_bytes_total
+                .get_or_create(&labels)
+                .inc_by((total_msgs_bytes - fb_msgs_bytes_total - serving_msgs_bytes_total) as u64);
+            pipeline_metrics()
+                .forwarder
+                .write_processing_time
+                .get_or_create(&labels)
+                .observe(write_start_time.elapsed().as_micros() as f64);
+            pipeline_metrics()
+                .forwarder
+                .write_error_total
+                .get_or_create(&labels)
+                .inc_by((write_errors_total) as u64);
         }
 
         Ok(())
@@ -627,7 +666,7 @@ impl SinkWriter {
     /// Handles the post retry failures based on the configured strategy,
     /// returns true if we need to retry, else false.
     fn handle_sink_post_retry(
-        attempts: &mut u16,
+        retry_attempts: &mut u16,
         error_map: &mut HashMap<String, i32>,
         fallback_msgs: &mut Vec<Message>,
         messages_to_send: &mut Vec<Message>,
@@ -643,8 +682,9 @@ impl SinkWriter {
             // if we need to retry, return true
             OnFailureStrategy::Retry => {
                 warn!(
-                    "Using onFailure Retry, Retry attempts {} completed",
-                    attempts
+                    retry_attempts = *retry_attempts,
+                    errors = ?error_map,
+                    "Using onFailure Retry, retry attempts completed"
                 );
                 return Ok(true);
             }
@@ -652,8 +692,9 @@ impl SinkWriter {
             OnFailureStrategy::Drop => {
                 // log that we are dropping the messages as requested
                 warn!(
-                    "Dropping messages after {} attempts. Errors: {:?}",
-                    attempts, error_map
+                    retry_attempts = *retry_attempts,
+                    errors = ?error_map,
+                    "Dropping messages."
                 );
                 if is_mono_vertex() {
                     // update the drop metric count with the messages left for sink
@@ -662,14 +703,25 @@ impl SinkWriter {
                         .dropped_total
                         .get_or_create(mvtx_forward_metric_labels())
                         .inc_by((messages_to_send.len()) as u64);
+                } else {
+                    pipeline_metrics()
+                        .forwarder
+                        .drop_total
+                        .get_or_create(&pipeline_drop_metric_labels(
+                            VERTEX_TYPE_SINK,
+                            get_vertex_name(),
+                            "retries exhausted in the Sink",
+                        ))
+                        .inc_by((messages_to_send.len()) as u64);
                 }
             }
             // if we need to move the messages to the fallback, return false
             OnFailureStrategy::Fallback => {
                 // log that we are moving the messages to the fallback as requested
                 warn!(
-                    "Moving messages to fallback after {} attempts. Errors: {:?}",
-                    attempts, error_map
+                    retry_attempts = *retry_attempts,
+                    errors = ?error_map,
+                    "Moving messages to fallback after retry attempts.",
                 );
                 // move the messages to the fallback messages
                 fallback_msgs.append(messages_to_send);
@@ -687,7 +739,6 @@ impl SinkWriter {
         fallback_msgs: &mut Vec<Message>,
         serving_msgs: &mut Vec<Message>,
         messages_to_send: &mut Vec<Message>,
-        retry_config: &RetryConfig,
     ) -> Result<bool> {
         match self.sink(messages_to_send.clone()).await {
             Ok(response) => {
@@ -728,11 +779,6 @@ impl SinkWriter {
                     return Ok(true);
                 }
 
-                sleep(Duration::from_millis(
-                    retry_config.sink_retry_interval_in_ms as u64,
-                ))
-                .await;
-
                 // we need to retry
                 Ok(false)
             }
@@ -753,7 +799,7 @@ impl SinkWriter {
             ));
         }
 
-        let mut attempts = 0;
+        let mut retry_attempts = 0;
         let mut fallback_error_map = HashMap::new();
         // start with the original set of message to be sent.
         // we will overwrite this vec with failed messages and will keep retrying.
@@ -764,10 +810,11 @@ impl SinkWriter {
             .clone()
             .backoff
             .unwrap();
-        let max_attempts = default_retry.steps.unwrap();
+        // steps is the maximum number of retry attempts, excluding the initial attempt
+        let max_retry_attempts = default_retry.steps.unwrap();
         let sleep_interval = default_retry.interval.unwrap();
 
-        while attempts < max_attempts {
+        while retry_attempts <= max_retry_attempts {
             match self.fb_sink(messages_to_send.clone()).await {
                 Ok(fb_response) => {
                     // create a map of id to result, since there is no strict requirement
@@ -790,6 +837,20 @@ impl SinkWriter {
                                 ResponseStatusFromSink::Success => false,
                                 ResponseStatusFromSink::Failed(err_msg) => {
                                     *fallback_error_map.entry(err_msg.clone()).or_insert(0) += 1;
+                                    // increment fb sink error metric for pipeline
+                                    if !is_mono_vertex() {
+                                        let mut labels =
+                                            pipeline_metric_labels(VERTEX_TYPE_SINK).clone();
+                                        labels.push((
+                                            PIPELINE_PARTITION_NAME_LABEL.to_string(),
+                                            get_vertex_name().to_string(),
+                                        ));
+                                        pipeline_metrics()
+                                            .sink_forwarder
+                                            .fbsink_write_error_total
+                                            .get_or_create(&labels)
+                                            .inc_by(1);
+                                    }
                                     true
                                 }
                                 ResponseStatusFromSink::Fallback => {
@@ -823,26 +884,33 @@ impl SinkWriter {
                         ));
                     }
 
-                    attempts += 1;
-
                     if messages_to_send.is_empty() {
                         break;
                     }
 
-                    warn!(
-                        "Retry attempt {} due to retryable error. Errors: {:?}",
-                        attempts, fallback_error_map
-                    );
-                    sleep(tokio::time::Duration::from(sleep_interval)).await;
+                    // increment retry attempt only if we will retry
+                    // otherwise break out of the loop
+                    // sleep for the calculated delay only if we are retrying
+                    if retry_attempts < max_retry_attempts {
+                        retry_attempts += 1;
+                        warn!(
+                            retry_attempts,
+                            ?fallback_error_map,
+                            "Retrying due to retryable error in Fallback Sink"
+                        );
+                        sleep(tokio::time::Duration::from(sleep_interval)).await;
+                    } else {
+                        break;
+                    }
                 }
                 Err(e) => return Err(e),
             }
         }
         if !messages_to_send.is_empty() {
             return Err(Error::FbSink(format!(
-                "Failed to write messages to fallback sink after {} attempts. \
+                "Failed to write messages to fallback sink after {} retry attempts. \
                 Max Attempts configured: {} Errors: {:?}",
-                attempts, max_attempts, fallback_error_map
+                retry_attempts, max_retry_attempts, fallback_error_map
             )));
         }
         Ok(())
@@ -896,7 +964,11 @@ impl SinkWriter {
         self.health_check_clients.ready().await
     }
 
-    fn send_fb_sink_metrics(fb_msgs_total: usize, fallback_sink_start: time::Instant) {
+    fn send_fb_sink_metrics(
+        fb_msgs_total: usize,
+        fb_msgs_bytes_total: usize,
+        fallback_sink_start: time::Instant,
+    ) {
         if is_mono_vertex() {
             monovertex_metrics()
                 .fb_sink
@@ -908,7 +980,55 @@ impl SinkWriter {
                 .time
                 .get_or_create(mvtx_forward_metric_labels())
                 .observe(fallback_sink_start.elapsed().as_micros() as f64);
+        } else {
+            let mut labels = pipeline_metric_labels(VERTEX_TYPE_SINK).clone();
+            labels.push((
+                PIPELINE_PARTITION_NAME_LABEL.to_string(),
+                get_vertex_name().to_string(),
+            ));
+            pipeline_metrics()
+                .sink_forwarder
+                .fbsink_write_total
+                .get_or_create(&labels)
+                .inc_by(fb_msgs_total as u64);
+            pipeline_metrics()
+                .sink_forwarder
+                .fbsink_write_bytes_total
+                .get_or_create(&labels)
+                .inc_by(fb_msgs_bytes_total as u64);
+
+            pipeline_metrics()
+                .sink_forwarder
+                .fbsink_write_processing_time
+                .get_or_create(&labels)
+                .observe(fallback_sink_start.elapsed().as_micros() as f64);
+        }
+    }
+
+    fn calculate_exponential_delay(
+        retry_config: &RetryConfig,
+        retry_attempts: u16,
+        rng: &mut StdRng,
+    ) -> f64 {
+        // Calculate the base delay using the initial retry interval and the retry factor
+        // The base delay is calculated as: initial_retry_interval * retry_factor^(retry_attempts-1)
+        let base_delay = (retry_config.sink_initial_retry_interval_in_ms as f64)
+            * retry_config
+                .sink_retry_factor
+                .powi((retry_attempts - 1) as i32);
+
+        let jitter = retry_config.sink_retry_jitter;
+        // If jitter is 0, return the base delay
+        // and cap it to the max retry interval
+        if jitter == 0.0 {
+            return base_delay.min(retry_config.sink_max_retry_interval_in_ms as f64);
         }
+
+        // Apply jitter to the base delay
+        // jitter is a value between 0 and 1
+        // 1.0 - jitter gives us the lower bound and 1.0 + jitter gives us the upper bound
+        let jitter_factor: f64 = rng.gen_range(1.0 - jitter..=1.0 + jitter);
+        (base_delay * jitter_factor).min(retry_config.sink_max_retry_interval_in_ms as f64)
     }
 }
 
@@ -1024,6 +1144,7 @@ mod tests {
                 },
                 headers: HashMap::new(),
                 metadata: None,
+                is_late: false,
             })
             .collect();
 
@@ -1061,6 +1182,7 @@ mod tests {
                 },
                 headers: HashMap::new(),
                 metadata: None,
+                is_late: false,
             })
             .collect();
 
@@ -1139,6 +1261,7 @@ mod tests {
                 },
                 headers: HashMap::new(),
                 metadata: None,
+                is_late: false,
             })
             .collect();
 
@@ -1227,6 +1350,7 @@ mod tests {
                 },
                 headers: HashMap::new(),
                 metadata: None,
+                is_late: false,
             })
             .collect();
 
@@ -1340,6 +1464,7 @@ mod tests {
                     },
                     headers,
                     metadata: None,
+                    is_late: false,
                 }
             })
             .collect();
@@ -1388,6 +1513,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         let request: SinkRequest = message.into();
@@ -1420,4 +1546,55 @@ mod tests {
             ResponseStatusFromSink::Success
         );
     }
+
+    #[test]
+    fn test_calculate_exponential_delay_no_jitter() {
+        let retry_config = RetryConfig {
+            sink_initial_retry_interval_in_ms: 100,
+            sink_retry_factor: 2.0,
+            sink_retry_jitter: 0.0,
+            sink_max_retry_interval_in_ms: 1000,
+            ..Default::default()
+        };
+
+        let mut rng = StdRng::from_entropy();
+        let delay = SinkWriter::calculate_exponential_delay(&retry_config, 3, &mut rng);
+        // Base delay: 100 * 2^(3-1) = 400
+        assert_eq!(delay, 400.0);
+    }
+
+    #[test]
+    fn test_calculate_exponential_delay_with_jitter() {
+        let retry_config = RetryConfig {
+            sink_initial_retry_interval_in_ms: 100,
+            sink_retry_factor: 2.0,
+            sink_retry_jitter: 0.5,
+            sink_max_retry_interval_in_ms: 1000,
+            ..Default::default()
+        };
+
+        let mut rng = StdRng::from_entropy();
+        let delay = SinkWriter::calculate_exponential_delay(&retry_config, 3, &mut rng);
+
+        // Base delay: 100 * 2^(3-1) = 400
+        // Jitter range: 400 * (1.0 - 0.5) to 400 * (1.0 + 0.5) = 200 to 600
+        assert!(delay >= 200.0 && delay <= 600.0);
+    }
+
+    #[test]
+    fn test_calculate_exponential_delay_max_cap() {
+        let retry_config = RetryConfig {
+            sink_initial_retry_interval_in_ms: 100,
+            sink_retry_factor: 10.0,
+            sink_retry_jitter: 0.0,
+            sink_max_retry_interval_in_ms: 1000,
+            ..Default::default()
+        };
+
+        let mut rng = StdRng::from_entropy();
+        let delay = SinkWriter::calculate_exponential_delay(&retry_config, 3, &mut rng);
+
+        // Base delay: 100 * 10^(3-1) = 100000, but capped at 1000
+        assert_eq!(delay, 1000.0);
+    }
 }
diff --git a/rust/numaflow-core/src/sink/blackhole.rs b/rust/numaflow-core/src/sink/blackhole.rs
index 7598242d..c1853907 100644
--- a/rust/numaflow-core/src/sink/blackhole.rs
+++ b/rust/numaflow-core/src/sink/blackhole.rs
@@ -46,8 +46,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 0,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
             Message {
                 typ: Default::default(),
@@ -62,8 +61,7 @@ mod tests {
                     offset: "2".to_string().into(),
                     index: 1,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
         ];
 
diff --git a/rust/numaflow-core/src/sink/kafka.rs b/rust/numaflow-core/src/sink/kafka.rs
new file mode 100644
index 00000000..d41843f3
--- /dev/null
+++ b/rust/numaflow-core/src/sink/kafka.rs
@@ -0,0 +1,127 @@
+use numaflow_kafka::sink::{KafkaSink, KafkaSinkMessage, KafkaSinkResponse};
+
+use crate::error::{Error, Result};
+use crate::message::Message;
+use crate::sink::{ResponseFromSink, ResponseStatusFromSink, Sink};
+
+impl TryFrom<Message> for KafkaSinkMessage {
+    type Error = Error;
+
+    fn try_from(mut msg: Message) -> Result<Self> {
+        let id = msg.id.to_string();
+        // Add all message keys to the Kafka headers
+        msg.headers
+            .insert("__key_len".to_string(), msg.keys.len().to_string());
+        for (i, key) in msg.keys.iter().enumerate() {
+            msg.headers.insert(format!("__key_{i}"), key.to_string());
+        }
+
+        let partition_key = if msg.keys.is_empty() {
+            None
+        } else {
+            Some(msg.keys.join(":"))
+        };
+
+        Ok(Self {
+            id,
+            partition_key,
+            headers: msg.headers,
+            payload: msg.value,
+        })
+    }
+}
+
+impl From<KafkaSinkResponse> for ResponseFromSink {
+    fn from(resp: KafkaSinkResponse) -> Self {
+        match &resp.status {
+            Ok(_) => ResponseFromSink {
+                id: resp.id,
+                status: ResponseStatusFromSink::Success,
+                serve_response: None,
+            },
+            Err(e) => ResponseFromSink {
+                id: resp.id,
+                status: ResponseStatusFromSink::Failed(e.to_string()),
+                serve_response: None,
+            },
+        }
+    }
+}
+
+impl Sink for KafkaSink {
+    async fn sink(&mut self, messages: Vec<Message>) -> Result<Vec<ResponseFromSink>> {
+        let kafka_messages: Vec<KafkaSinkMessage> = messages
+            .into_iter()
+            .map(TryInto::try_into)
+            .collect::<Result<_>>()?;
+        Ok(self
+            .sink_messages(kafka_messages)
+            .await
+            .map_err(|e| Error::Sink(e.to_string()))?
+            .into_iter()
+            .map(|msg| msg.into())
+            .collect::<Vec<ResponseFromSink>>())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::message::{IntOffset, Message, MessageID, Offset};
+    use bytes::Bytes;
+    use chrono::Utc;
+    use numaflow_kafka::sink::test_utils;
+    use numaflow_kafka::sink::{KafkaSinkConfig, new_sink};
+    use std::collections::HashMap;
+    use std::sync::Arc;
+
+    #[cfg(feature = "kafka-tests")]
+    #[tokio::test]
+    async fn test_kafka_sink_multiple_messages() {
+        let (_producer, topic_name) = test_utils::setup_test_topic().await;
+        let config = KafkaSinkConfig {
+            brokers: vec!["localhost:9092".to_string()],
+            topic: topic_name.clone(),
+            auth: None,
+            tls: None,
+            set_partition_key: false,
+        };
+        let mut sink = new_sink(config).expect("Failed to create KafkaSink");
+        let mut messages = Vec::new();
+        for i in 0..5 {
+            let mut headers = HashMap::new();
+            headers.insert("header".to_string(), format!("value{}", i));
+            messages.push(Message {
+                typ: Default::default(),
+                keys: Arc::from(vec![]),
+                tags: None,
+                value: Bytes::from(format!("payload-{}", i)),
+                offset: Offset::Int(IntOffset::new(i, 0)),
+                event_time: Utc::now(),
+                watermark: None,
+                id: MessageID {
+                    vertex_name: "vertex".to_string().into(),
+                    offset: i.to_string().into(),
+                    index: i as i32,
+                },
+                headers,
+                metadata: None,
+                is_late: false,
+            });
+        }
+        let responses = sink.sink(messages).await.expect("Failed to send messages");
+        assert_eq!(responses.len(), 5);
+        for resp in responses {
+            assert!(matches!(resp.status, ResponseStatusFromSink::Success));
+        }
+        // Consume all messages
+        let messages = test_utils::consume_messages_from_topic(&topic_name, 5).await;
+        let mut seen_payloads = std::collections::HashSet::new();
+        for msg in messages {
+            seen_payloads.insert(String::from_utf8_lossy(&msg.payload).to_string());
+        }
+        for i in 0..5 {
+            assert!(seen_payloads.contains(&format!("payload-{}", i)));
+        }
+    }
+}
diff --git a/rust/numaflow-core/src/sink/log.rs b/rust/numaflow-core/src/sink/log.rs
index aac63b0b..fa1e8ef4 100644
--- a/rust/numaflow-core/src/sink/log.rs
+++ b/rust/numaflow-core/src/sink/log.rs
@@ -59,8 +59,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 0,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
             Message {
                 typ: Default::default(),
@@ -75,8 +74,7 @@ mod tests {
                     offset: "2".to_string().into(),
                     index: 1,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
         ];
 
diff --git a/rust/numaflow-core/src/sink/serve.rs b/rust/numaflow-core/src/sink/serve.rs
index a40f3d47..ce193887 100644
--- a/rust/numaflow-core/src/sink/serve.rs
+++ b/rust/numaflow-core/src/sink/serve.rs
@@ -76,8 +76,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 0,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
             Message {
                 typ: Default::default(),
@@ -92,8 +91,7 @@ mod tests {
                     offset: "2".to_string().into(),
                     index: 1,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
         ];
 
diff --git a/rust/numaflow-core/src/sink/sqs.rs b/rust/numaflow-core/src/sink/sqs.rs
new file mode 100644
index 00000000..3f3c39ea
--- /dev/null
+++ b/rust/numaflow-core/src/sink/sqs.rs
@@ -0,0 +1,354 @@
+use numaflow_sqs::sink::{SqsSink, SqsSinkMessage};
+
+use crate::error;
+use crate::error::Error;
+use crate::message::Message;
+use crate::sink::{ResponseFromSink, ResponseStatusFromSink, Sink};
+
+impl TryFrom<Message> for SqsSinkMessage {
+    type Error = error::Error;
+
+    fn try_from(msg: Message) -> crate::Result<Self> {
+        let id = msg.id.to_string();
+        Ok(SqsSinkMessage {
+            id,
+            message_body: msg.value,
+        })
+    }
+}
+
+impl From<numaflow_sqs::SqsSinkError> for Error {
+    fn from(value: numaflow_sqs::SqsSinkError) -> Self {
+        match value {
+            numaflow_sqs::SqsSinkError::Error(numaflow_sqs::Error::Sqs(e)) => {
+                Error::Sink(e.to_string())
+            }
+            numaflow_sqs::SqsSinkError::Error(numaflow_sqs::Error::ActorTaskTerminated(_)) => {
+                Error::ActorPatternRecv(value.to_string())
+            }
+            numaflow_sqs::SqsSinkError::Error(numaflow_sqs::Error::InvalidConfig(e)) => {
+                Error::Sink(e)
+            }
+            numaflow_sqs::SqsSinkError::Error(numaflow_sqs::Error::Other(e)) => Error::Sink(e),
+        }
+    }
+}
+
+impl Sink for SqsSink {
+    async fn sink(&mut self, messages: Vec<Message>) -> error::Result<Vec<ResponseFromSink>> {
+        let mut result = Vec::with_capacity(messages.len());
+
+        let sqs_messages: Vec<SqsSinkMessage> = messages
+            .iter()
+            .map(|msg| SqsSinkMessage::try_from(msg.clone()))
+            .collect::<Result<Vec<_>, _>>()?;
+
+        let sqs_sink_result = self.sink_messages(sqs_messages).await;
+
+        if sqs_sink_result.is_err() {
+            return Err(Error::from(sqs_sink_result.err().unwrap()));
+        }
+        for sqs_response in sqs_sink_result?.iter() {
+            match &sqs_response.status {
+                Ok(_) => {
+                    result.push(ResponseFromSink {
+                        id: sqs_response.id.clone(),
+                        status: ResponseStatusFromSink::Success,
+                        serve_response: None,
+                    });
+                }
+                Err(err) => {
+                    result.push(ResponseFromSink {
+                        id: sqs_response.id.clone(),
+                        status: ResponseStatusFromSink::Failed(err.to_string()),
+                        serve_response: None,
+                    });
+                }
+            }
+        }
+        Ok(result)
+    }
+}
+
+#[cfg(feature = "sqs-tests")]
+#[cfg(test)]
+pub mod tests {
+    use std::collections::HashSet;
+    use std::sync::atomic::{AtomicUsize, Ordering};
+    use std::time::Duration;
+
+    use crate::monovertex::forwarder::Forwarder;
+    use crate::shared::grpc::create_rpc_channel;
+    use crate::sink::SinkWriter;
+    use crate::source::user_defined::new_source;
+    use crate::source::{Source, SourceType};
+    use crate::tracker::TrackerHandle;
+    use aws_sdk_sqs::Config;
+    use aws_sdk_sqs::config::BehaviorVersion;
+    use aws_sdk_sqs::types::BatchResultErrorEntry;
+    use aws_smithy_mocks_experimental::{MockResponseInterceptor, Rule, RuleMode, mock};
+    use chrono::Utc;
+    use numaflow::source;
+    use numaflow::source::{Message, Offset, SourceReadRequest};
+    use numaflow_pb::clients::source::source_client::SourceClient;
+    use numaflow_sqs::sink::{SQS_DEFAULT_REGION, SqsSinkBuilder, SqsSinkConfig};
+    use tempfile::TempDir;
+    use tokio::sync::mpsc::Sender;
+    use tokio::sync::oneshot;
+    use tokio::task::JoinHandle;
+    use tokio_util::sync::CancellationToken;
+
+    struct SimpleSource {
+        num: usize,
+        sent_count: AtomicUsize,
+        yet_to_ack: std::sync::RwLock<HashSet<String>>,
+    }
+
+    impl SimpleSource {
+        fn new(num: usize) -> Self {
+            Self {
+                num,
+                sent_count: AtomicUsize::new(0),
+                yet_to_ack: std::sync::RwLock::new(HashSet::new()),
+            }
+        }
+    }
+
+    #[tonic::async_trait]
+    impl source::Sourcer for SimpleSource {
+        async fn read(&self, request: SourceReadRequest, transmitter: Sender<Message>) {
+            let event_time = Utc::now();
+            let mut message_offsets = Vec::with_capacity(request.count);
+
+            for i in 0..request.count {
+                if self.sent_count.load(Ordering::SeqCst) >= self.num {
+                    return;
+                }
+
+                // create a unique predictable offset for each message using a fixed prefix
+                // and the message index
+                let prefix = "source-testsinke2e-";
+
+                let offset = format!("{}-{}", prefix, i);
+                transmitter
+                    .send(Message {
+                        value: b"hello".to_vec(),
+                        event_time,
+                        offset: Offset {
+                            offset: offset.clone().into_bytes(),
+                            partition_id: 0,
+                        },
+                        keys: vec![],
+                        headers: Default::default(),
+                    })
+                    .await
+                    .unwrap();
+                message_offsets.push(offset);
+                self.sent_count.fetch_add(1, Ordering::SeqCst);
+            }
+            self.yet_to_ack.write().unwrap().extend(message_offsets);
+        }
+
+        async fn ack(&self, offsets: Vec<Offset>) {
+            for offset in offsets {
+                self.yet_to_ack
+                    .write()
+                    .unwrap()
+                    .remove(&String::from_utf8(offset.offset).unwrap());
+            }
+        }
+
+        async fn pending(&self) -> Option<usize> {
+            Some(
+                self.num - self.sent_count.load(Ordering::SeqCst)
+                    + self.yet_to_ack.read().unwrap().len(),
+            )
+        }
+
+        async fn partitions(&self) -> Option<Vec<i32>> {
+            Some(vec![1, 2])
+        }
+    }
+
+    #[tokio::test]
+    async fn test_sqs_sink_e2e() {
+        let tracker_handle = TrackerHandle::new(None, None);
+        let (source, src_handle, src_shutdown_tx) = get_simple_source(tracker_handle.clone()).await;
+        // let source = get_sqs_source().await;
+        let sink_writer = get_sqs_sink(tracker_handle.clone()).await;
+        // create the forwarder with the source, transformer, and writer
+        let forwarder = Forwarder::new(source.clone(), sink_writer);
+
+        let cln_token = CancellationToken::new();
+        let cancel_token = cln_token.clone();
+        let forwarder_handle: JoinHandle<crate::error::Result<()>> = tokio::spawn(async move {
+            forwarder.start(cancel_token).await?;
+            Ok(())
+        });
+
+        // wait for one sec to check if the pending becomes zero, because all the messages
+        // should be read and acked; if it doesn't, then fail the test
+        let tokio_result = tokio::time::timeout(Duration::from_secs(1), async move {
+            loop {
+                let pending = source.pending().await.unwrap();
+                if pending == Some(0) {
+                    break;
+                }
+                tokio::time::sleep(Duration::from_millis(10)).await;
+            }
+        })
+        .await;
+
+        assert!(
+            tokio_result.is_ok(),
+            "Timeout occurred before pending became zero"
+        );
+        cln_token.cancel();
+        forwarder_handle.await.unwrap().unwrap();
+        src_shutdown_tx.send(()).unwrap();
+        src_handle.await.unwrap();
+    }
+
+    async fn get_simple_source(
+        tracker_handle: TrackerHandle,
+    ) -> (Source, JoinHandle<()>, oneshot::Sender<()>) {
+        let (src_shutdown_tx, src_shutdown_rx) = oneshot::channel();
+        let tmp_dir = TempDir::new().unwrap();
+        let sock_file = tmp_dir.path().join("source.sock");
+        let server_info_file = tmp_dir.path().join("source-server-info");
+
+        let server_info = server_info_file.clone();
+        let server_socket = sock_file.clone();
+        let source_handle = tokio::spawn(async move {
+            // a simple source which generates total of 100 messages
+            source::Server::new(SimpleSource::new(5))
+                .with_socket_file(server_socket)
+                .with_server_info_file(server_info)
+                .start_with_shutdown(src_shutdown_rx)
+                .await
+                .unwrap()
+        });
+
+        // wait for the server to start
+        // TODO: flaky
+        tokio::time::sleep(Duration::from_millis(50)).await;
+
+        let client = SourceClient::new(create_rpc_channel(sock_file).await.unwrap());
+
+        let (src_read, src_ack, lag_reader) = new_source(client, 5, Duration::from_millis(100))
+            .await
+            .map_err(|e| panic!("failed to create source reader: {:?}", e))
+            .unwrap();
+        (
+            Source::new(
+                5,
+                SourceType::UserDefinedSource(src_read, src_ack, lag_reader),
+                tracker_handle.clone(),
+                true,
+                None,
+                None,
+            ),
+            source_handle,
+            src_shutdown_tx,
+        )
+    }
+
+    async fn get_sqs_sink(tracker_handle: TrackerHandle) -> SinkWriter {
+        let queue_url_output = get_queue_url_output();
+        let sqs_operation_mocks = MockResponseInterceptor::new()
+            .rule_mode(RuleMode::MatchAny)
+            .with_rule(&queue_url_output)
+            .with_rule(&get_send_message_output(5));
+
+        let sqs_client =
+            aws_sdk_sqs::Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
+
+        let sqs_sink = SqsSinkBuilder::new(SqsSinkConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "12345678912",
+        })
+        .client(sqs_client)
+        .build()
+        .await
+        .unwrap();
+
+        // create sink writer
+        use crate::sink::{SinkClientType, SinkWriterBuilder};
+        SinkWriterBuilder::new(
+            5,
+            Duration::from_millis(100),
+            SinkClientType::Sqs(sqs_sink),
+            tracker_handle.clone(),
+        )
+        .build()
+        .await
+        .unwrap()
+    }
+
+    fn get_send_message_output(count: i32) -> Rule {
+        let mut successful = vec![];
+        for i in 0..count {
+            let msg_id = format!("msg-id-source-testsinke2e-{}", i.clone());
+            let id = format!("source-testsinke2e-{}", i.clone());
+            let entry = aws_sdk_sqs::types::SendMessageBatchResultEntry::builder()
+                .id(id)
+                .message_id(msg_id)
+                .md5_of_message_body("f11a425906289abf8cce1733622834c8")
+                .build()
+                .unwrap();
+
+            successful.push(entry);
+        }
+
+        let send_message_output = mock!(aws_sdk_sqs::Client::send_message_batch)
+            .match_requests(|inp| {
+                inp.queue_url().unwrap()
+                    == "https://sqs.us-west-2.amazonaws.com/926113353675/test-q/"
+            })
+            .then_output(move || {
+                // Create a vector of successful entries
+                let successful_entries = successful.clone();
+
+                // Create an empty vector for failed entries
+                let failed_entries: Vec<BatchResultErrorEntry> = Vec::new();
+
+                aws_sdk_sqs::operation::send_message_batch::SendMessageBatchOutput::builder()
+                    .set_successful(Some(successful_entries))
+                    .set_failed(Some(failed_entries))
+                    .build()
+                    .unwrap()
+            });
+        send_message_output
+    }
+
+    fn get_queue_url_output() -> Rule {
+        let queue_url_output = mock!(aws_sdk_sqs::Client::get_queue_url)
+            .match_requests(|inp| inp.queue_name().unwrap() == "test-q")
+            .then_output(|| {
+                aws_sdk_sqs::operation::get_queue_url::GetQueueUrlOutput::builder()
+                    .queue_url("https://sqs.us-west-2.amazonaws.com/926113353675/test-q/")
+                    .build()
+            });
+        queue_url_output
+    }
+
+    fn get_test_config_with_interceptor(interceptor: MockResponseInterceptor) -> Config {
+        Config::builder()
+            .behavior_version(BehaviorVersion::latest())
+            .credentials_provider(make_sqs_test_credentials())
+            .region(aws_sdk_sqs::config::Region::new("us-west-2"))
+            .interceptor(interceptor)
+            .build()
+    }
+
+    fn make_sqs_test_credentials() -> aws_sdk_sqs::config::Credentials {
+        aws_sdk_sqs::config::Credentials::new(
+            "ATESTCLIENT",
+            "astestsecretkey",
+            Some("atestsessiontoken".to_string()),
+            None,
+            "",
+        )
+    }
+}
diff --git a/rust/numaflow-core/src/sink/user_defined.rs b/rust/numaflow-core/src/sink/user_defined.rs
index 352a8f4e..9e88424e 100644
--- a/rust/numaflow-core/src/sink/user_defined.rs
+++ b/rust/numaflow-core/src/sink/user_defined.rs
@@ -233,8 +233,7 @@ mod tests {
                     offset: "1".to_string().into(),
                     index: 0,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
             Message {
                 typ: Default::default(),
@@ -249,8 +248,7 @@ mod tests {
                     offset: "2".to_string().into(),
                     index: 1,
                 },
-                watermark: None,
-                metadata: None,
+                ..Default::default()
             },
         ];
 
diff --git a/rust/numaflow-core/src/source.rs b/rust/numaflow-core/src/source.rs
index 9da47e29..dfb1db19 100644
--- a/rust/numaflow-core/src/source.rs
+++ b/rust/numaflow-core/src/source.rs
@@ -4,23 +4,25 @@
 //! [Source]: https://numaflow.numaproj.io/user-guide/sources/overview/
 //! [Watermark]: https://numaflow.numaproj.io/core-concepts/watermarks/
 
+use crate::config::pipeline::VERTEX_TYPE_SOURCE;
 use crate::config::{get_vertex_name, is_mono_vertex};
 use crate::error::{Error, Result};
 use crate::message::ReadAck;
 use crate::metrics::{
-    monovertex_metrics, mvtx_forward_metric_labels, pipeline_forward_metric_labels,
-    pipeline_isb_metric_labels, pipeline_metrics,
+    PIPELINE_PARTITION_NAME_LABEL, monovertex_metrics, mvtx_forward_metric_labels,
+    pipeline_isb_metric_labels, pipeline_metric_labels, pipeline_metrics,
 };
 use crate::tracker::TrackerHandle;
 use crate::{
     message::{Message, Offset},
-    metrics,
     reader::LagReader,
 };
+use chrono::Utc;
 use numaflow_jetstream::JetstreamSource;
+use numaflow_kafka::source::KafkaSource;
 use numaflow_pb::clients::source::source_client::SourceClient;
 use numaflow_pulsar::source::PulsarSource;
-use numaflow_sqs::source::SQSSource;
+use numaflow_sqs::source::SqsSource;
 use std::sync::Arc;
 use tokio::sync::OwnedSemaphorePermit;
 use tokio::sync::Semaphore;
@@ -52,6 +54,8 @@ pub(crate) mod jetstream;
 
 pub(crate) mod sqs;
 
+pub(crate) mod kafka;
+
 use crate::transformer::Transformer;
 use crate::watermark::source::SourceWatermarkHandle;
 
@@ -87,10 +91,9 @@ pub(crate) enum SourceType {
         generator::GeneratorLagReader,
     ),
     Pulsar(PulsarSource),
-    #[allow(clippy::upper_case_acronyms)]
-    #[allow(dead_code)] // TODO(SQS): remove it when integrated with controller
-    SQS(SQSSource),
+    Sqs(SqsSource),
     Jetstream(JetstreamSource),
+    Kafka(KafkaSource),
 }
 
 enum ActorMessage {
@@ -227,7 +230,7 @@ impl Source {
                     actor.run().await;
                 });
             }
-            SourceType::SQS(sqs_source) => {
+            SourceType::Sqs(sqs_source) => {
                 tokio::spawn(async move {
                     let actor = SourceActor::new(
                         receiver,
@@ -245,6 +248,12 @@ impl Source {
                     actor.run().await;
                 });
             }
+            SourceType::Kafka(kafka) => {
+                tokio::spawn(async move {
+                    let actor = SourceActor::new(receiver, kafka.clone(), kafka.clone(), kafka);
+                    actor.run().await;
+                });
+            }
         };
 
         Self {
@@ -317,9 +326,9 @@ impl Source {
     ) -> Result<(ReceiverStream<Message>, JoinHandle<Result<()>>)> {
         let (messages_tx, messages_rx) = mpsc::channel(2 * self.read_batch_size);
 
-        let mut pipeline_labels = pipeline_forward_metric_labels("Source").clone();
+        let mut pipeline_labels = pipeline_metric_labels(VERTEX_TYPE_SOURCE).clone();
         pipeline_labels.push((
-            metrics::PIPELINE_PARTITION_NAME_LABEL.to_string(),
+            PIPELINE_PARTITION_NAME_LABEL.to_string(),
             get_vertex_name().to_string(),
         ));
 
@@ -336,6 +345,8 @@ impl Source {
                 false => 1,
             };
             let semaphore = Arc::new(Semaphore::new(max_ack_tasks));
+            let mut processed_msgs_count: usize = 0;
+            let mut last_logged_at = Instant::now();
 
             let mut result = Ok(());
             loop {
@@ -362,7 +373,14 @@ impl Source {
                 };
 
                 let msgs_len = messages.len();
-                Self::send_read_metrics(&pipeline_labels, mvtx_labels, read_start_time, msgs_len);
+                let msgs_bytes = messages.iter().map(|msg| msg.value.len()).sum();
+                Self::send_read_metrics(
+                    &pipeline_labels,
+                    mvtx_labels,
+                    read_start_time,
+                    msgs_len,
+                    msgs_bytes,
+                );
 
                 // attempt to publish idle watermark since we are not able to read any message from
                 // the source.
@@ -404,7 +422,7 @@ impl Source {
 
                 // transform the batch if the transformer is present, this need not
                 // be streaming because transformation should be fast operation.
-                let messages = match self.transformer.as_mut() {
+                let mut messages = match self.transformer.as_mut() {
                     None => messages,
                     Some(transformer) => match transformer
                         .transform_batch(messages, cln_token.clone())
@@ -432,6 +450,12 @@ impl Source {
                     watermark_handle
                         .generate_and_publish_source_watermark(&messages)
                         .await;
+
+                    let watermark = watermark_handle.fetch_source_watermark().await;
+                    // compare with the event time of the message and set is_late
+                    for message in messages.iter_mut() {
+                        message.is_late = message.event_time < watermark;
+                    }
                 }
 
                 // write the messages to downstream.
@@ -440,6 +464,17 @@ impl Source {
                         .send(message)
                         .await
                         .expect("send should not fail");
+
+                    processed_msgs_count += 1;
+                    if last_logged_at.elapsed().as_secs() >= 1 {
+                        info!(
+                            "Processed {} messages in {:?}",
+                            processed_msgs_count,
+                            Utc::now()
+                        );
+                        processed_msgs_count = 0;
+                        last_logged_at = Instant::now();
+                    }
                 }
             }
             info!(status=?result, "Source stopped, waiting for inflight messages to be acked");
@@ -494,6 +529,7 @@ impl Source {
         mvtx_labels: &Vec<(String, String)>,
         read_start_time: Instant,
         n: usize,
+        msgs_bytes: usize,
     ) {
         if is_mono_vertex() {
             monovertex_metrics()
@@ -512,7 +548,22 @@ impl Source {
                 .inc_by(n as u64);
             pipeline_metrics()
                 .forwarder
-                .read_time
+                .data_read_total
+                .get_or_create(pipeline_labels)
+                .inc_by(n as u64);
+            pipeline_metrics()
+                .forwarder
+                .read_bytes_total
+                .get_or_create(pipeline_labels)
+                .inc_by(msgs_bytes as u64);
+            pipeline_metrics()
+                .forwarder
+                .data_read_bytes_total
+                .get_or_create(pipeline_labels)
+                .inc_by(msgs_bytes as u64);
+            pipeline_metrics()
+                .forwarder
+                .read_processing_time
                 .get_or_create(pipeline_labels)
                 .observe(read_start_time.elapsed().as_micros() as f64);
         }
@@ -537,7 +588,7 @@ impl Source {
         } else {
             pipeline_metrics()
                 .forwarder
-                .ack_time
+                .ack_processing_time
                 .get_or_create(pipeline_isb_metric_labels())
                 .observe(start.elapsed().as_micros() as f64);
 
@@ -549,7 +600,7 @@ impl Source {
 
             pipeline_metrics()
                 .forwarder
-                .processed_time
+                .forward_chunk_processing_time
                 .get_or_create(pipeline_isb_metric_labels())
                 .observe(e2e_start_time.elapsed().as_micros() as f64);
         }
diff --git a/rust/numaflow-core/src/source/generator.rs b/rust/numaflow-core/src/source/generator.rs
index 48f731cb..c8fa17e5 100644
--- a/rust/numaflow-core/src/source/generator.rs
+++ b/rust/numaflow-core/src/source/generator.rs
@@ -78,8 +78,10 @@ mod stream_generator {
             let key_count = std::cmp::min(cfg.key_count as usize, cfg.rpu) as u8;
             if key_count != cfg.key_count {
                 warn!(
-                    "Specified KeyCount({}) is higher than RPU ({}). KeyCount is changed to {}",
-                    cfg.key_count, cfg.rpu, key_count
+                    key_count = cfg.key_count,
+                    rpu = cfg.rpu,
+                    new_key_count = key_count,
+                    "Specified KeyCount is higher than RPU. KeyCount has changed."
                 );
             }
             if key_count > 0 && rpu % key_count as usize != 0 {
@@ -87,8 +89,8 @@ mod stream_generator {
                 warn!(
                     rpu,
                     key_count,
-                    "Specified RPU is not a multiple of the KeyCount. This may lead to uneven distribution of messages across keys. RPUs will be adjusted to {}",
-                    new_rpu
+                    new_rpu,
+                    "Specified RPU is not a multiple of the KeyCount. This may lead to uneven distribution of messages across keys. RPUs will be adjusted."
                 );
                 rpu = new_rpu;
             }
@@ -184,6 +186,7 @@ mod stream_generator {
                 },
                 headers: Default::default(),
                 metadata: None,
+                is_late: false,
             }
         }
 
diff --git a/rust/numaflow-core/src/source/jetstream.rs b/rust/numaflow-core/src/source/jetstream.rs
index e0cd533d..9c20087f 100644
--- a/rust/numaflow-core/src/source/jetstream.rs
+++ b/rust/numaflow-core/src/source/jetstream.rs
@@ -34,6 +34,7 @@ impl From<numaflow_jetstream::Message> for Message {
             metadata: Some(Metadata {
                 previous_vertex: get_vertex_name().to_string(),
             }),
+            is_late: false,
         }
     }
 }
diff --git a/rust/numaflow-core/src/source/kafka.rs b/rust/numaflow-core/src/source/kafka.rs
new file mode 100644
index 00000000..96405f3f
--- /dev/null
+++ b/rust/numaflow-core/src/source/kafka.rs
@@ -0,0 +1,227 @@
+use std::sync::Arc;
+use std::time::Duration;
+
+use numaflow_kafka::source::{KafkaMessage, KafkaSource, KafkaSourceConfig};
+
+use crate::config::{get_vertex_name, get_vertex_replica};
+use crate::error::Error;
+use crate::message::{Message, MessageID, Offset, StringOffset};
+use crate::source;
+
+impl TryFrom<KafkaMessage> for Message {
+    type Error = Error;
+
+    fn try_from(message: KafkaMessage) -> crate::Result<Self> {
+        let offset = Offset::String(StringOffset::new(
+            format!("{}:{}:{}", message.topic, message.partition, message.offset),
+            *get_vertex_replica(),
+        ));
+
+        Ok(Message {
+            typ: Default::default(),
+            keys: Arc::from(vec![]),
+            tags: None,
+            value: message.value,
+            offset: offset.clone(),
+            event_time: Default::default(),
+            watermark: None,
+            id: MessageID {
+                vertex_name: get_vertex_name().to_string().into(),
+                offset: offset.to_string().into(),
+                index: 0,
+            },
+            headers: message.headers,
+            metadata: None,
+            is_late: false,
+        })
+    }
+}
+
+impl From<numaflow_kafka::Error> for Error {
+    fn from(value: numaflow_kafka::Error) -> Self {
+        match value {
+            numaflow_kafka::Error::Kafka(e) => Error::Source(e.to_string()),
+            numaflow_kafka::Error::Connection { server, error } => Error::Source(format!(
+                "Failed to connect to Kafka server: {server} - {error}"
+            )),
+            numaflow_kafka::Error::Other(e) => Error::Source(e),
+        }
+    }
+}
+
+pub(crate) async fn new_kafka_source(
+    cfg: KafkaSourceConfig,
+    batch_size: usize,
+    timeout: Duration,
+) -> crate::Result<KafkaSource> {
+    Ok(KafkaSource::connect(cfg, batch_size, timeout).await?)
+}
+
+impl source::SourceReader for KafkaSource {
+    fn name(&self) -> &'static str {
+        "Kafka"
+    }
+
+    async fn read(&mut self) -> crate::Result<Vec<Message>> {
+        self.read_messages()
+            .await?
+            .into_iter()
+            .map(|msg| msg.try_into())
+            .collect()
+    }
+
+    async fn partitions(&mut self) -> crate::error::Result<Vec<u16>> {
+        let partitions = self.partitions_info().await?;
+        Ok(partitions.into_iter().map(|p| p as u16).collect())
+    }
+}
+
+impl source::SourceAcker for KafkaSource {
+    async fn ack(&mut self, offsets: Vec<Offset>) -> crate::error::Result<()> {
+        let mut kafka_offsets = Vec::with_capacity(offsets.len());
+        for offset in offsets {
+            let Offset::String(string_offset) = offset else {
+                return Err(Error::Source(format!(
+                    "Expected Offset::String type for Kafka. offset={offset:?}"
+                )));
+            };
+
+            let offset = String::from_utf8_lossy(&string_offset.offset);
+            let parts: Vec<&str> = offset.split(':').collect();
+            if parts.len() != 3 {
+                return Err(Error::Source(format!(
+                    "Invalid Kafka offset format. Expected format: <topic>:<partition>:<offset>. offset={offset:?}"
+                )));
+            }
+            let topic = parts[0].to_string();
+            let partition = parts[1].parse::<i32>().map_err(|e| {
+                Error::Source(format!(
+                    "invalid partition id. kafka_offset={offset}, error={e:?}"
+                ))
+            })?;
+
+            let partition_offset = parts[2].parse::<i64>().map_err(|e| {
+                Error::Source(format!(
+                    "invalid offset id. kafka_offset={offset}, error={e:?}"
+                ))
+            })?;
+            kafka_offsets.push(numaflow_kafka::source::KafkaOffset {
+                topic,
+                partition,
+                offset: partition_offset,
+            });
+        }
+        self.ack_messages(kafka_offsets).await.map_err(Into::into)
+    }
+}
+
+impl source::LagReader for KafkaSource {
+    async fn pending(&mut self) -> crate::error::Result<Option<usize>> {
+        Ok(self.pending_messages().await?)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use bytes::Bytes;
+    use numaflow_kafka::source::{KafkaMessage, test_utils};
+    use std::collections::HashMap;
+
+    #[tokio::test]
+    async fn test_try_from_kafka_message_success() {
+        let kafka_message = KafkaMessage {
+            topic: "test_topic".to_string(),
+            value: Bytes::from("test_value"),
+            partition: 1,
+            offset: 42,
+            headers: {
+                let mut headers = HashMap::new();
+                headers.insert("key".to_string(), "value".to_string());
+                headers
+            },
+        };
+
+        let message: Message = kafka_message.try_into().unwrap();
+
+        assert_eq!(message.value, Bytes::from("test_value"));
+        assert_eq!(message.offset.to_string(), "test_topic:1:42-0");
+        assert_eq!(message.headers.get("key"), Some(&"value".to_string()));
+    }
+
+    #[cfg(feature = "kafka-tests")]
+    #[tokio::test]
+    async fn test_kafka_source_reader_acker_lagreader() {
+        use crate::{
+            reader::LagReader,
+            source::{SourceAcker, SourceReader},
+        };
+
+        let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
+
+        // Setup Kafka producer and topic
+        let (producer, topic_name) = test_utils::setup_test_topic().await;
+
+        // Publish messages to the topic
+        test_utils::produce_test_messages(&producer, &topic_name, 50).await;
+
+        // Configure KafkaSource
+        let config = numaflow_kafka::source::KafkaSourceConfig {
+            brokers: vec!["localhost:9092".to_string()],
+            topics: vec![topic_name.clone()],
+            consumer_group: "test_consumer_group".to_string(),
+            auth: None,
+            tls: None,
+        };
+
+        let read_timeout = Duration::from_secs(5);
+        let mut source = super::new_kafka_source(config, 20, read_timeout)
+            .await
+            .unwrap();
+
+        assert_eq!(source.partitions().await.unwrap(), vec![0]);
+
+        // Test SourceReader::read
+        let messages = source.read().await.unwrap();
+        assert_eq!(messages.len(), 20, "Should read 20 messages in a batch");
+        assert_eq!(messages[0].value, Bytes::from("message 0"));
+        assert_eq!(messages[19].value, Bytes::from("message 19"));
+
+        // Test SourceAcker::ack
+        let offsets: Vec<Offset> = messages.iter().map(|msg| msg.offset.clone()).collect();
+        source.ack(offsets).await.unwrap();
+
+        // Test LagReader::pending
+        let pending = source.pending().await.unwrap();
+        assert_eq!(
+            pending,
+            Some(30),
+            "Pending messages should be 30 after acking 20 messages"
+        );
+
+        // Read and ack remaining messages
+        let messages = source.read().await.unwrap();
+        assert_eq!(messages.len(), 20, "Should read another 20 messages");
+        let offsets: Vec<Offset> = messages.iter().map(|msg| msg.offset.clone()).collect();
+        source.ack(offsets).await.unwrap();
+
+        let pending = source.pending().await.unwrap();
+        assert_eq!(
+            pending,
+            Some(10),
+            "Pending messages should be 10 after acking another 20 messages"
+        );
+
+        let messages = source.read().await.unwrap();
+        assert_eq!(messages.len(), 10, "Should read the last 10 messages");
+        let offsets: Vec<Offset> = messages.iter().map(|msg| msg.offset.clone()).collect();
+        source.ack(offsets).await.unwrap();
+
+        let pending = source.pending().await.unwrap();
+        assert_eq!(
+            pending,
+            Some(0),
+            "Pending messages should be 0 after acking all messages"
+        );
+    }
+}
diff --git a/rust/numaflow-core/src/source/pulsar.rs b/rust/numaflow-core/src/source/pulsar.rs
index 7bdded5b..e6dcc7d0 100644
--- a/rust/numaflow-core/src/source/pulsar.rs
+++ b/rust/numaflow-core/src/source/pulsar.rs
@@ -29,6 +29,7 @@ impl TryFrom<PulsarMessage> for Message {
             },
             headers: message.headers,
             metadata: None,
+            is_late: false,
         })
     }
 }
diff --git a/rust/numaflow-core/src/source/sqs.rs b/rust/numaflow-core/src/source/sqs.rs
index e5e8e27c..b3c1bde7 100644
--- a/rust/numaflow-core/src/source/sqs.rs
+++ b/rust/numaflow-core/src/source/sqs.rs
@@ -1,17 +1,16 @@
+use numaflow_sqs::source::{SqsMessage, SqsSource, SqsSourceBuilder, SqsSourceConfig};
 use std::sync::Arc;
 use std::time::Duration;
 
-use numaflow_sqs::source::{SQSMessage, SQSSource, SQSSourceConfig, SqsSourceBuilder};
-
 use crate::config::{get_vertex_name, get_vertex_replica};
 use crate::error::Error;
 use crate::message::{Message, MessageID, Offset, StringOffset};
 use crate::source;
 
-impl TryFrom<SQSMessage> for Message {
+impl TryFrom<SqsMessage> for Message {
     type Error = Error;
 
-    fn try_from(message: SQSMessage) -> crate::Result<Self> {
+    fn try_from(message: SqsMessage) -> crate::Result<Self> {
         let offset = Offset::String(StringOffset::new(message.offset, *get_vertex_replica()));
 
         Ok(Message {
@@ -29,29 +28,34 @@ impl TryFrom<SQSMessage> for Message {
             },
             headers: message.headers,
             metadata: None,
+            is_late: false,
         })
     }
 }
 
-impl From<numaflow_sqs::Error> for Error {
-    fn from(value: numaflow_sqs::Error) -> Self {
+impl From<numaflow_sqs::SqsSourceError> for Error {
+    fn from(value: numaflow_sqs::SqsSourceError) -> Self {
         match value {
-            numaflow_sqs::Error::Sqs(e) => Error::Source(e.to_string()),
-            numaflow_sqs::Error::ActorTaskTerminated(_) => {
+            numaflow_sqs::SqsSourceError::Error(numaflow_sqs::Error::Sqs(e)) => {
+                Error::Source(e.to_string())
+            }
+            numaflow_sqs::SqsSourceError::Error(numaflow_sqs::Error::ActorTaskTerminated(_)) => {
                 Error::ActorPatternRecv(value.to_string())
             }
-            numaflow_sqs::Error::InvalidConfig(e) => Error::Source(e),
-            numaflow_sqs::Error::Other(e) => Error::Source(e),
+            numaflow_sqs::SqsSourceError::Error(numaflow_sqs::Error::InvalidConfig(e)) => {
+                Error::Source(e)
+            }
+            numaflow_sqs::SqsSourceError::Error(numaflow_sqs::Error::Other(e)) => Error::Source(e),
         }
     }
 }
 
 pub(crate) async fn new_sqs_source(
-    cfg: SQSSourceConfig,
+    cfg: SqsSourceConfig,
     batch_size: usize,
     timeout: Duration,
     vertex_replica: u16,
-) -> crate::Result<SQSSource> {
+) -> crate::Result<SqsSource> {
     Ok(SqsSourceBuilder::new(cfg)
         .batch_size(batch_size)
         .timeout(timeout)
@@ -60,9 +64,9 @@ pub(crate) async fn new_sqs_source(
         .await?)
 }
 
-impl source::SourceReader for SQSSource {
+impl source::SourceReader for SqsSource {
     fn name(&self) -> &'static str {
-        "Sqs"
+        "SQS"
     }
 
     async fn read(&mut self) -> crate::Result<Vec<Message>> {
@@ -79,7 +83,7 @@ impl source::SourceReader for SQSSource {
     }
 }
 
-impl source::SourceAcker for SQSSource {
+impl source::SourceAcker for SqsSource {
     async fn ack(&mut self, offsets: Vec<Offset>) -> crate::error::Result<()> {
         let mut sqs_offsets = Vec::with_capacity(offsets.len());
         for offset in offsets {
@@ -94,7 +98,7 @@ impl source::SourceAcker for SQSSource {
     }
 }
 
-impl source::LagReader for SQSSource {
+impl source::LagReader for SqsSource {
     async fn pending(&mut self) -> crate::error::Result<Option<usize>> {
         Ok(self.pending_count().await)
     }
@@ -124,7 +128,7 @@ pub mod tests {
         let mut headers = HashMap::new();
         headers.insert("foo".to_string(), "bar".to_string());
 
-        let sqs_message = SQSMessage {
+        let sqs_message = SqsMessage {
             key: "key".to_string(),
             payload: Bytes::from("value".to_string()),
             offset: "offset".to_string(),
@@ -147,7 +151,7 @@ pub mod tests {
     }
 
     #[tokio::test]
-    async fn test_sqs_e2e() {
+    async fn test_sqs_source_e2e() {
         let queue_url_output = get_queue_url_output();
 
         let receive_message_output = get_receive_message_output();
@@ -166,10 +170,10 @@ pub mod tests {
         let sqs_client =
             aws_sdk_sqs::Client::from_conf(get_test_config_with_interceptor(sqs_operation_mocks));
 
-        let sqs_source = SqsSourceBuilder::new(SQSSourceConfig {
-            region: SQS_DEFAULT_REGION.to_string(),
-            queue_name: "test-q".to_string(),
-            queue_owner_aws_account_id: "12345678912".to_string(),
+        let sqs_source = SqsSourceBuilder::new(SqsSourceConfig {
+            region: SQS_DEFAULT_REGION,
+            queue_name: "test-q",
+            queue_owner_aws_account_id: "12345678912",
             visibility_timeout: None,
             max_number_of_messages: None,
             wait_time_seconds: None,
@@ -189,7 +193,7 @@ pub mod tests {
         let tracker_handle = TrackerHandle::new(None, None);
         let source = Source::new(
             1,
-            SourceType::SQS(sqs_source),
+            SourceType::Sqs(sqs_source),
             tracker_handle.clone(),
             true,
             None,
diff --git a/rust/numaflow-core/src/source/user_defined.rs b/rust/numaflow-core/src/source/user_defined.rs
index f6bcd0f1..6aa1be47 100644
--- a/rust/numaflow-core/src/source/user_defined.rs
+++ b/rust/numaflow-core/src/source/user_defined.rs
@@ -162,6 +162,7 @@ impl TryFrom<read_response::Result> for Message {
             headers: result.headers,
             watermark: None,
             metadata: None,
+            is_late: false,
         })
     }
 }
diff --git a/rust/numaflow-core/src/tracker.rs b/rust/numaflow-core/src/tracker.rs
index 97dfa536..ec523c59 100644
--- a/rust/numaflow-core/src/tracker.rs
+++ b/rust/numaflow-core/src/tracker.rs
@@ -219,7 +219,7 @@ impl Tracker {
             },
         );
 
-        if let Some(watermark_handle) = &self.watermark_handle {
+        if let Some(watermark_handle) = self.watermark_handle.as_mut() {
             watermark_handle.insert_offset(offset, watermark).await;
         }
     }
@@ -293,7 +293,7 @@ impl Tracker {
             .ack_send
             .send(ReadAck::Nak)
             .expect("Failed to send nak");
-        if let Some(watermark_handle) = &self.watermark_handle {
+        if let Some(watermark_handle) = self.watermark_handle.as_mut() {
             watermark_handle.remove_offset(offset).await;
         }
     }
@@ -316,7 +316,7 @@ impl Tracker {
     /// - ack back
     /// - call serving callbacks
     /// - watermark progression
-    async fn completed_successfully(&self, offset: Offset, entry: TrackerEntry) {
+    async fn completed_successfully(&mut self, offset: Offset, entry: TrackerEntry) {
         let TrackerEntry {
             ack_send,
             serving_callback_info: callback_info,
@@ -325,7 +325,7 @@ impl Tracker {
 
         ack_send.send(ReadAck::Ack).expect("Failed to send ack");
 
-        if let Some(watermark_handle) = &self.watermark_handle {
+        if let Some(watermark_handle) = self.watermark_handle.as_mut() {
             watermark_handle.remove_offset(offset).await;
         }
 
@@ -524,6 +524,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         let callback_info: super::Result<ServingCallbackInfo> = TryFrom::try_from(&message);
@@ -566,6 +567,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         // Insert a new message
@@ -607,6 +609,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         // Insert a new message
@@ -653,6 +656,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         // Insert a new message
@@ -688,6 +692,7 @@ mod tests {
             },
             headers: HashMap::new(),
             metadata: None,
+            is_late: false,
         };
 
         // Insert a new message
@@ -756,6 +761,7 @@ mod tests {
             metadata: Some(Metadata {
                 previous_vertex: "source-vertex".into(),
             }),
+            is_late: false,
         };
 
         // Insert a new message
diff --git a/rust/numaflow-core/src/transformer.rs b/rust/numaflow-core/src/transformer.rs
index c290ed3a..f071b51c 100644
--- a/rust/numaflow-core/src/transformer.rs
+++ b/rust/numaflow-core/src/transformer.rs
@@ -9,9 +9,14 @@ use tonic::{Code, Status};
 use tracing::error;
 
 use crate::Result;
+use crate::config::pipeline::VERTEX_TYPE_SOURCE;
+use crate::config::{get_vertex_name, is_mono_vertex};
 use crate::error::Error;
 use crate::message::Message;
-use crate::metrics::{monovertex_metrics, mvtx_forward_metric_labels};
+use crate::metrics::{
+    PIPELINE_PARTITION_NAME_LABEL, monovertex_metrics, mvtx_forward_metric_labels,
+    pipeline_metric_labels, pipeline_metrics,
+};
 use crate::tracker::TrackerHandle;
 use crate::transformer::user_defined::UserDefinedTransformer;
 
@@ -100,8 +105,6 @@ impl Transformer {
         read_msg: Message,
         cln_token: CancellationToken,
     ) -> Result<Vec<Message>> {
-        let start_time = tokio::time::Instant::now();
-
         let (sender, receiver) = oneshot::channel();
         let msg = TransformerActorMessage {
             message: read_msg,
@@ -139,12 +142,6 @@ impl Transformer {
             futures::future::pending::<()>().await;
         }
 
-        monovertex_metrics()
-            .transformer
-            .time
-            .get_or_create(mvtx_forward_metric_labels())
-            .observe(start_time.elapsed().as_micros() as f64);
-
         Ok(response)
     }
 
@@ -154,9 +151,24 @@ impl Transformer {
         messages: Vec<Message>,
         cln_token: CancellationToken,
     ) -> Result<Vec<Message>> {
+        let batch_start_time = tokio::time::Instant::now();
         let transform_handle = self.sender.clone();
         let tracker_handle = self.tracker_handle.clone();
         let semaphore = Arc::new(Semaphore::new(self.concurrency));
+        let mut labels = pipeline_metric_labels(VERTEX_TYPE_SOURCE).clone();
+        labels.push((
+            PIPELINE_PARTITION_NAME_LABEL.to_string(),
+            get_vertex_name().to_string(),
+        ));
+
+        // increment read message count for pipeline
+        if !is_mono_vertex() {
+            pipeline_metrics()
+                .source_forwarder
+                .transformer_read_total
+                .get_or_create(&labels)
+                .inc_by(messages.len() as u64);
+        }
 
         let tasks: Vec<_> = messages
             .into_iter()
@@ -200,23 +212,72 @@ impl Transformer {
         for task in tasks {
             match task.await {
                 Ok(Ok(mut msgs)) => transformed_messages.append(&mut msgs),
-                Ok(Err(e)) => return Err(e),
+                Ok(Err(e)) => {
+                    // increment transform error metric for pipeline
+                    // error here indicates that there was some problem in transformation
+                    if !is_mono_vertex() {
+                        pipeline_metrics()
+                            .source_forwarder
+                            .transformer_error_total
+                            .get_or_create(&labels)
+                            .inc();
+                    }
+                    return Err(e);
+                }
                 Err(e) => return Err(Error::Transformer(format!("task join failed: {}", e))),
             }
         }
+        // batch transformation was successful
+        // send transformer metrics
         let dropped_messages_count = transformed_messages
             .iter()
             .filter(|message| message.dropped())
             .count();
+        let elapsed_time = batch_start_time.elapsed().as_micros() as f64;
+        let write_messages_count = transformed_messages.len() - dropped_messages_count;
+        Self::send_transformer_metrics(
+            dropped_messages_count,
+            elapsed_time,
+            write_messages_count,
+            &labels,
+        );
+        Ok(transformed_messages)
+    }
 
-        if dropped_messages_count > 0 {
+    fn send_transformer_metrics(
+        dropped_messages_count: usize,
+        elapsed_time: f64,
+        write_messages_count: usize,
+        labels: &Vec<(String, String)>,
+    ) {
+        if is_mono_vertex() {
+            monovertex_metrics()
+                .transformer
+                .time
+                .get_or_create(mvtx_forward_metric_labels())
+                .observe(elapsed_time);
             monovertex_metrics()
                 .transformer
                 .dropped_total
                 .get_or_create(mvtx_forward_metric_labels())
                 .inc_by(dropped_messages_count as u64);
+        } else {
+            pipeline_metrics()
+                .source_forwarder
+                .transformer_processing_time
+                .get_or_create(labels)
+                .observe(elapsed_time);
+            pipeline_metrics()
+                .source_forwarder
+                .transformer_drop_total
+                .get_or_create(labels)
+                .inc_by(dropped_messages_count as u64);
+            pipeline_metrics()
+                .source_forwarder
+                .transformer_write_total
+                .get_or_create(labels)
+                .inc_by(write_messages_count as u64);
         }
-        Ok(transformed_messages)
     }
 
     pub(crate) async fn ready(&mut self) -> bool {
@@ -303,6 +364,7 @@ mod tests {
             },
             headers: Default::default(),
             metadata: None,
+            is_late: false,
         };
 
         let transformed_messages = Transformer::transform(
@@ -374,6 +436,7 @@ mod tests {
                 },
                 headers: Default::default(),
                 metadata: None,
+                is_late: false,
             };
             messages.push(message);
         }
@@ -452,6 +515,7 @@ mod tests {
             },
             headers: Default::default(),
             metadata: None,
+            is_late: false,
         };
 
         let result = transformer
diff --git a/rust/numaflow-core/src/transformer/user_defined.rs b/rust/numaflow-core/src/transformer/user_defined.rs
index 23a1017b..d4f8a3be 100644
--- a/rust/numaflow-core/src/transformer/user_defined.rs
+++ b/rust/numaflow-core/src/transformer/user_defined.rs
@@ -21,6 +21,7 @@ type ResponseSenderMap =
 // fields which will not be changed
 struct ParentMessageInfo {
     offset: Offset,
+    is_late: bool,
     headers: HashMap<String, String>,
 }
 
@@ -150,6 +151,7 @@ impl UserDefinedTransformer {
                         headers: msg_info.headers.clone(),
                         watermark: None,
                         metadata: None,
+                        is_late: msg_info.is_late,
                     };
                     response_messages.push(message);
                 }
@@ -171,6 +173,7 @@ impl UserDefinedTransformer {
         let msg_info = ParentMessageInfo {
             offset: message.offset.clone(),
             headers: message.headers.clone(),
+            is_late: message.is_late,
         };
 
         self.senders
@@ -251,8 +254,7 @@ mod tests {
                 offset: "0".to_string().into(),
                 index: 0,
             },
-            headers: Default::default(),
-            metadata: None,
+            ..Default::default()
         };
 
         let (tx, rx) = oneshot::channel();
@@ -298,8 +300,7 @@ mod tests {
                 offset: "123".to_string().into(),
                 index: 0,
             },
-            headers: HashMap::new(),
-            metadata: None,
+            ..Default::default()
         };
 
         let request: SourceTransformRequest = message.into();
diff --git a/rust/numaflow-core/src/watermark/idle/source.rs b/rust/numaflow-core/src/watermark/idle/source.rs
index 5a99db22..d49bec26 100644
--- a/rust/numaflow-core/src/watermark/idle/source.rs
+++ b/rust/numaflow-core/src/watermark/idle/source.rs
@@ -20,6 +20,7 @@ use tracing::warn;
 use crate::config::pipeline::watermark::IdleConfig;
 
 /// Responsible for detecting the idle state of the source and publishing idle watermarks.
+#[derive(Clone)]
 pub(crate) struct SourceIdleDetector {
     config: IdleConfig,
     /// last_idle_wm_published_time is for comparing with the step interval
diff --git a/rust/numaflow-core/src/watermark/isb.rs b/rust/numaflow-core/src/watermark/isb.rs
index 83564bb6..09dd29ec 100644
--- a/rust/numaflow-core/src/watermark/isb.rs
+++ b/rust/numaflow-core/src/watermark/isb.rs
@@ -21,8 +21,10 @@
 //! ```
 use std::cmp::Ordering;
 use std::collections::{BTreeSet, HashMap};
+use std::sync::Arc;
+use std::sync::Mutex;
 use std::time::Duration;
-
+use tokio::sync::mpsc;
 use tokio::sync::mpsc::Receiver;
 use tokio_util::sync::CancellationToken;
 use tracing::error;
@@ -32,6 +34,7 @@ use crate::config::pipeline::isb::Stream;
 use crate::config::pipeline::watermark::EdgeWatermarkConfig;
 use crate::error::{Error, Result};
 use crate::message::{IntOffset, Offset};
+use crate::reduce::reducer::aligned::windower::WindowManager;
 use crate::watermark::idle::isb::ISBIdleDetector;
 use crate::watermark::isb::wm_fetcher::ISBWatermarkFetcher;
 use crate::watermark::isb::wm_publisher::ISBWatermarkPublisher;
@@ -43,24 +46,23 @@ pub(crate) mod wm_publisher;
 
 /// Messages that can be sent to the [ISBWatermarkActor].
 enum ISBWaterMarkActorMessage {
-    FetchWatermark {
+    Fetch {
         offset: IntOffset,
         oneshot_tx: tokio::sync::oneshot::Sender<Result<Watermark>>,
     },
-    PublishWatermark {
-        offset: IntOffset,
+    Publish {
         stream: Stream,
+        offset: i64,
+        watermark: i64,
+        is_idle: bool,
     },
-    RemoveOffset(IntOffset),
-    InsertOffset {
-        offset: IntOffset,
-        watermark: Watermark,
+    FetchHeadIdle {
+        oneshot_tx: tokio::sync::oneshot::Sender<Result<Watermark>>,
     },
-    CheckAndPublishIdleWatermark,
 }
 
 /// Tuple of offset and watermark. We will use this to track the inflight messages.
-#[derive(Eq, PartialEq, Debug)]
+#[derive(Eq, PartialEq, Debug, Clone)]
 struct OffsetWatermark {
     /// offset can be -1 if watermark cannot be derived.
     offset: i64,
@@ -81,158 +83,113 @@ impl PartialOrd for OffsetWatermark {
 }
 
 /// EdgeWatermarkActor comprises EdgeFetcher and EdgePublisher.
-/// Tracks the watermarks of all the inflight messages for each partition, and publishes
-/// the lowest watermark.
+/// Only responsible for the actual fetch and publish operations.
 struct ISBWatermarkActor {
     fetcher: ISBWatermarkFetcher,
     publisher: ISBWatermarkPublisher,
-    /// BTreeSet is used to track the watermarks of the inflight messages because we frequently
-    /// need to get the lowest watermark among the inflight messages and BTreeSet provides O(1)
-    /// time complexity for getting the lowest watermark, even though insertion and deletion are
-    /// O(log n). If we use map or hashset, our lowest watermark fetch call would be O(n) even
-    /// though insertion and deletion are O(1). We do almost same amount insertion, deletion and
-    /// getting the lowest watermark so BTreeSet is the best choice.
-    offset_set: HashMap<u16, BTreeSet<OffsetWatermark>>,
-    idle_manager: ISBIdleDetector,
 }
 
 impl ISBWatermarkActor {
-    fn new(
-        fetcher: ISBWatermarkFetcher,
-        publisher: ISBWatermarkPublisher,
-        idle_manager: ISBIdleDetector,
-    ) -> Self {
-        Self {
-            fetcher,
-            publisher,
-            offset_set: HashMap::new(),
-            idle_manager,
-        }
+    fn new(fetcher: ISBWatermarkFetcher, publisher: ISBWatermarkPublisher) -> Self {
+        Self { fetcher, publisher }
     }
 
     /// run listens for messages and handles them
     async fn run(mut self, mut receiver: Receiver<ISBWaterMarkActorMessage>) {
         while let Some(message) = receiver.recv().await {
             if let Err(e) = self.handle_message(message).await {
-                error!("error handling message: {:?}", e);
+                error!("error handling watermark actor message: {:?}", e);
             }
         }
     }
 
-    /// handle_message handles the incoming actor message.
     async fn handle_message(&mut self, message: ISBWaterMarkActorMessage) -> Result<()> {
         match message {
             // fetches the watermark for the given offset
-            ISBWaterMarkActorMessage::FetchWatermark { offset, oneshot_tx } => {
-                let watermark = self
-                    .fetcher
-                    .fetch_watermark(offset.offset, offset.partition_idx);
-
-                oneshot_tx
-                    .send(Ok(watermark))
-                    .map_err(|_| Error::Watermark("failed to send response".to_string()))?;
-            }
-
-            // gets the lowest watermark among the inflight requests and publishes the watermark
-            // for the offset and stream
-            ISBWaterMarkActorMessage::PublishWatermark { offset, stream } => {
-                let min_wm = self
-                    .get_lowest_watermark()
-                    .unwrap_or(Watermark::from_timestamp_millis(-1).unwrap());
-
-                self.publisher
-                    .publish_watermark(&stream, offset.offset, min_wm.timestamp_millis(), false)
-                    .await;
-
-                self.idle_manager.reset_idle(&stream).await;
+            ISBWaterMarkActorMessage::Fetch { offset, oneshot_tx } => {
+                self.handle_fetch_watermark(offset, oneshot_tx).await
             }
 
-            // removes the offset from the tracked offsets
-            ISBWaterMarkActorMessage::RemoveOffset(offset) => {
-                self.remove_offset(offset.partition_idx, offset.offset)?;
+            // publishes the watermark for the given stream and offset
+            ISBWaterMarkActorMessage::Publish {
+                stream,
+                offset,
+                watermark,
+                is_idle,
+            } => {
+                self.handle_publish_watermark(stream, offset, watermark, is_idle)
+                    .await
             }
 
-            // inserts the offset to the tracked offsets
-            ISBWaterMarkActorMessage::InsertOffset { offset, watermark } => {
-                self.insert_offset(offset.partition_idx, offset.offset, watermark);
-            }
-
-            // check for idleness and publish idle watermark for those downstream idle partitions
-            ISBWaterMarkActorMessage::CheckAndPublishIdleWatermark => {
-                // if there are any inflight messages, consider the lowest watermark among them
-                let mut min_wm = self
-                    .get_lowest_watermark()
-                    .unwrap_or(Watermark::from_timestamp_millis(-1).expect("failed to parse time"));
-
-                // if there are no inflight messages, use the head idle watermark
-                if min_wm.timestamp_millis() == -1 {
-                    min_wm = self.fetcher.fetch_head_idle_watermark();
-                }
-
-                // we are not able to compute WM, pod restarts, etc.
-                if min_wm.timestamp_millis() == -1 {
-                    return Ok(());
-                }
-
-                // identify the streams that are idle.
-                let idle_streams = self.idle_manager.fetch_idle_streams().await;
-
-                // publish the idle watermark for the idle partitions
-                for stream in idle_streams.iter() {
-                    let offset = match self.idle_manager.fetch_idle_offset(stream).await {
-                        Ok(offset) => offset,
-                        Err(e) => {
-                            error!("failed to fetch idle offset: {:?}", e);
-                            continue;
-                        }
-                    };
-                    self.publisher
-                        .publish_watermark(stream, offset, min_wm.timestamp_millis(), true)
-                        .await;
-                    self.idle_manager.update_idle_metadata(stream, offset).await;
-                }
+            // fetches the head idle watermark
+            ISBWaterMarkActorMessage::FetchHeadIdle { oneshot_tx } => {
+                self.handle_fetch_head_idle_watermark(oneshot_tx).await
             }
         }
-
-        Ok(())
     }
 
-    /// insert the offset and watermark for inflight requests set
-    fn insert_offset(&mut self, partition_id: u16, offset: i64, watermark: Watermark) {
-        let set = self.offset_set.entry(partition_id).or_default();
-        set.insert(OffsetWatermark { offset, watermark });
+    // fetches the watermark for the given offset and sends the response back via oneshot channel
+    async fn handle_fetch_watermark(
+        &mut self,
+        offset: IntOffset,
+        oneshot_tx: tokio::sync::oneshot::Sender<Result<Watermark>>,
+    ) -> Result<()> {
+        let watermark = self
+            .fetcher
+            .fetch_watermark(offset.offset, offset.partition_idx);
+
+        oneshot_tx
+            .send(Ok(watermark))
+            .map_err(|_| Error::Watermark("failed to send response".to_string()))
     }
 
-    /// removes the offset from the inflight offsets set of the partition
-    fn remove_offset(&mut self, partition_id: u16, offset: i64) -> Result<()> {
-        if let Some(set) = self.offset_set.get_mut(&partition_id) {
-            if let Some(&OffsetWatermark { watermark, .. }) =
-                set.iter().find(|ow| ow.offset == offset)
-            {
-                set.remove(&OffsetWatermark { offset, watermark });
-            }
-        }
+    // publishes the watermark for the given stream and offset
+    async fn handle_publish_watermark(
+        &mut self,
+        stream: Stream,
+        offset: i64,
+        watermark: i64,
+        is_idle: bool,
+    ) -> Result<()> {
+        self.publisher
+            .publish_watermark(&stream, offset, watermark, is_idle)
+            .await;
         Ok(())
     }
 
-    /// gets the lowest watermark among all the inflight requests
-    fn get_lowest_watermark(&self) -> Option<Watermark> {
-        self.offset_set
-            .values()
-            .filter_map(|set| set.iter().next().map(|ow| ow.watermark))
-            .min()
+    // fetches the head idle watermark and sends the response back via oneshot channel
+    async fn handle_fetch_head_idle_watermark(
+        &mut self,
+        oneshot_tx: tokio::sync::oneshot::Sender<Result<Watermark>>,
+    ) -> Result<()> {
+        let watermark = self.fetcher.fetch_head_idle_watermark();
+        oneshot_tx
+            .send(Ok(watermark))
+            .map_err(|_| Error::Watermark("failed to send response".to_string()))
     }
 }
 
 /// Handle to interact with the EdgeWatermarkActor, exposes methods to fetch and publish watermarks
-/// for the edges
+/// for the edges. Contains all the computation logic.
 #[derive(Clone)]
 pub(crate) struct ISBWatermarkHandle {
-    sender: tokio::sync::mpsc::Sender<ISBWaterMarkActorMessage>,
+    sender: mpsc::Sender<ISBWaterMarkActorMessage>,
+    /// BTreeSet is used to track the watermarks of the inflight messages because we frequently
+    /// need to get the lowest watermark among the inflight messages and BTreeSet provides O(1)
+    /// time complexity for getting the lowest watermark, even though insertion and deletion are
+    /// O(log n). If we use map or hashset, our lowest watermark fetch call would be O(n) even
+    /// though insertion and deletion are O(1). We do almost same amount insertion, deletion and
+    /// getting the lowest watermark so BTreeSet is the best choice.
+    offset_set: Arc<Mutex<HashMap<u16, BTreeSet<OffsetWatermark>>>>,
+    idle_manager: ISBIdleDetector,
+    /// Window manager is used to compute the minimum watermark for the reduce vertex.
+    window_manager: Option<WindowManager>,
+    latest_fetched_wm: Arc<Mutex<Watermark>>,
 }
 
 impl ISBWatermarkHandle {
     /// new creates a new [ISBWatermarkHandle]. We also start a background task to detect WM idleness.
+    #[allow(clippy::too_many_arguments)]
     pub(crate) async fn new(
         vertex_name: &'static str,
         vertex_replica: u16,
@@ -241,8 +198,9 @@ impl ISBWatermarkHandle {
         config: &EdgeWatermarkConfig,
         to_vertex_configs: &[ToVertexConfig],
         cln_token: CancellationToken,
+        window_manager: Option<WindowManager>,
     ) -> Result<Self> {
-        let (sender, receiver) = tokio::sync::mpsc::channel(100);
+        let (sender, receiver) = mpsc::channel(100);
 
         // create a processor manager map (from_vertex -> ProcessorManager)
         let mut processor_managers = HashMap::new();
@@ -265,14 +223,22 @@ impl ISBWatermarkHandle {
         let idle_manager =
             ISBIdleDetector::new(idle_timeout, to_vertex_configs, js_context.clone()).await;
 
-        let actor = ISBWatermarkActor::new(fetcher, publisher, idle_manager);
+        let actor = ISBWatermarkActor::new(fetcher, publisher);
         tokio::spawn(async move { actor.run(receiver).await });
 
-        let isb_watermark_handle = Self { sender };
+        let isb_watermark_handle = Self {
+            sender,
+            offset_set: Arc::new(Mutex::new(HashMap::new())),
+            idle_manager,
+            window_manager,
+            latest_fetched_wm: Arc::new(Mutex::new(
+                Watermark::from_timestamp_millis(-1).expect("failed to parse timestamp"),
+            )),
+        };
 
         // start a task to keep publishing idle watermarks every idle_timeout
         tokio::spawn({
-            let isb_watermark_handle = isb_watermark_handle.clone();
+            let mut isb_watermark_handle = isb_watermark_handle.clone();
             let mut interval_ticker = tokio::time::interval(idle_timeout);
             let cln_token = cln_token.clone();
             async move {
@@ -294,89 +260,199 @@ impl ISBWatermarkHandle {
 
     /// Fetches the watermark for the given offset, if we are not able to compute the watermark we
     /// return -1.
-    pub(crate) async fn fetch_watermark(&self, offset: Offset) -> Watermark {
-        if let Offset::Int(offset) = offset {
+    pub(crate) async fn fetch_watermark(&mut self, offset: Offset) -> Watermark {
+        let Offset::Int(offset) = offset else {
+            error!(?offset, "Invalid offset type, cannot compute watermark");
+            return Watermark::from_timestamp_millis(-1).expect("failed to parse time");
+        };
+
+        let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
+        if let Err(e) = self
+            .sender
+            .send(ISBWaterMarkActorMessage::Fetch { offset, oneshot_tx })
+            .await
+        {
+            error!(?e, "Failed to send message");
+            return Watermark::from_timestamp_millis(-1).expect("failed to parse time");
+        }
+
+        match oneshot_rx.await {
+            Ok(watermark) => {
+                let wm = watermark.unwrap_or_else(|e| {
+                    error!(?e, "Failed to fetch watermark");
+                    Watermark::from_timestamp_millis(-1).expect("failed to parse time")
+                });
+
+                // Update the latest fetched watermark
+                let mut latest_fetched_wm = self
+                    .latest_fetched_wm
+                    .lock()
+                    .expect("failed to acquire lock");
+                *latest_fetched_wm = std::cmp::max(wm, *latest_fetched_wm);
+
+                wm
+            }
+            Err(e) => {
+                error!(?e, "Failed to receive response");
+                Watermark::from_timestamp_millis(-1).expect("failed to parse time")
+            }
+        }
+    }
+
+    /// publish_watermark publishes the watermark for the given stream and offset.
+    pub(crate) async fn publish_watermark(&mut self, stream: Stream, offset: Offset) {
+        let Offset::Int(offset) = offset else {
+            error!(?offset, "Invalid offset type, cannot publish watermark");
+            return;
+        };
+
+        // Compute the minimum watermark
+        let min_wm = self.compute_min_watermark().await;
+
+        // Send the publish watermark message to the actor
+        self.sender
+            .send(ISBWaterMarkActorMessage::Publish {
+                stream: stream.clone(),
+                offset: offset.offset,
+                watermark: min_wm.timestamp_millis(),
+                is_idle: false,
+            })
+            .await
+            .unwrap_or_else(|e| {
+                error!("Failed to send message: {:?}", e);
+            });
+
+        // Reset idle state for this stream
+        self.idle_manager.reset_idle(&stream).await;
+    }
+
+    /// remove_offset removes the offset from the tracked offsets.
+    pub(crate) async fn remove_offset(&mut self, offset: Offset) {
+        let Offset::Int(offset) = offset else {
+            error!(?offset, "Invalid offset type, cannot remove offset");
+            return;
+        };
+
+        // Remove the offset from the tracked offsets
+        let mut offset_set = self.offset_set.lock().expect("failed to acquire lock");
+        if let Some(set) = offset_set.get_mut(&offset.partition_idx) {
+            if let Some(&OffsetWatermark { watermark, .. }) =
+                set.iter().find(|ow| ow.offset == offset.offset)
+            {
+                set.remove(&OffsetWatermark {
+                    offset: offset.offset,
+                    watermark,
+                });
+            }
+        }
+    }
+
+    /// insert_offset inserts the offset to the tracked offsets.
+    pub(crate) async fn insert_offset(&mut self, offset: Offset, watermark: Option<Watermark>) {
+        let Offset::Int(offset) = offset else {
+            error!(?offset, "Invalid offset type, cannot insert offset");
+            return;
+        };
+
+        let wm = watermark
+            .unwrap_or(Watermark::from_timestamp_millis(-1).expect("failed to parse time"));
+
+        // Insert the offset and watermark to the tracked offsets
+        let mut offset_set = self.offset_set.lock().expect("failed to acquire lock");
+        let set = offset_set.entry(offset.partition_idx).or_default();
+        set.insert(OffsetWatermark {
+            offset: offset.offset,
+            watermark: wm,
+        });
+    }
+
+    /// publishes the idle watermark for the downstream idle partitions.
+    pub(crate) async fn publish_idle_watermark(&mut self) {
+        // Compute the minimum watermark
+        let mut min_wm = self.compute_min_watermark().await;
+
+        // if the computed min watermark is -1, means there is no data (no windows and inflight messages)
+        // we should fetch the head idle watermark and publish it to downstream.
+        if min_wm.timestamp_millis() == -1 {
             let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
             if let Err(e) = self
                 .sender
-                .send(ISBWaterMarkActorMessage::FetchWatermark { offset, oneshot_tx })
+                .send(ISBWaterMarkActorMessage::FetchHeadIdle { oneshot_tx })
                 .await
             {
                 error!(?e, "Failed to send message");
-                return Watermark::from_timestamp_millis(-1).expect("failed to parse time");
+                return;
             }
 
-            match oneshot_rx.await {
+            min_wm = match oneshot_rx.await {
                 Ok(watermark) => watermark.unwrap_or_else(|e| {
-                    error!(?e, "Failed to fetch watermark");
+                    error!(?e, "Failed to fetch head idle watermark");
                     Watermark::from_timestamp_millis(-1).expect("failed to parse time")
                 }),
                 Err(e) => {
                     error!(?e, "Failed to receive response");
                     Watermark::from_timestamp_millis(-1).expect("failed to parse time")
                 }
-            }
-        } else {
-            error!(?offset, "Invalid offset type, cannot compute watermark");
-            Watermark::from_timestamp_millis(-1).expect("failed to parse time")
+            };
         }
-    }
 
-    /// publish_watermark publishes the watermark for the given stream and offset.
-    pub(crate) async fn publish_watermark(&self, stream: Stream, offset: Offset) {
-        if let Offset::Int(offset) = offset {
-            self.sender
-                .send(ISBWaterMarkActorMessage::PublishWatermark { offset, stream })
-                .await
-                .unwrap_or_else(|e| {
-                    error!("Failed to send message: {:?}", e);
-                });
-        } else {
-            error!(?offset, "Invalid offset type, cannot publish watermark");
+        // Identify the streams that are idle and publish the idle watermark
+        let idle_streams = self.idle_manager.fetch_idle_streams().await;
+        for stream in idle_streams.iter() {
+            if let Ok(offset) = self.idle_manager.fetch_idle_offset(stream).await {
+                // publish the watermark
+                self.sender
+                    .send(ISBWaterMarkActorMessage::Publish {
+                        stream: stream.clone(),
+                        offset,
+                        watermark: min_wm.timestamp_millis(),
+                        is_idle: true,
+                    })
+                    .await
+                    .unwrap_or_else(|e| {
+                        error!("Failed to send message: {:?}", e);
+                    });
+
+                self.idle_manager.update_idle_metadata(stream, offset).await;
+            }
         }
     }
 
-    /// remove_offset removes the offset from the tracked offsets.
-    pub(crate) async fn remove_offset(&self, offset: Offset) {
-        if let Offset::Int(offset) = offset {
-            self.sender
-                .send(ISBWaterMarkActorMessage::RemoveOffset(offset))
-                .await
-                .unwrap_or_else(|e| {
-                    error!("Failed to send message: {:?}", e);
-                });
-        } else {
-            error!(?offset, "Invalid offset type, cannot remove offset");
+    /// Computes the minimum watermark based on window manager and inflight messages.
+    async fn compute_min_watermark(&self) -> Watermark {
+        // If window manager is configured, we can use the oldest window's end time - 1ms as the
+        // watermark.
+        if let Some(window_manager) = &self.window_manager {
+            if let Some(oldest_window) = window_manager.oldest_window() {
+                // we should also compare it with the latest fetched watermark because sometimes
+                // the window end time can be greater than the watermark of the messages in the window.
+                // in that case we should use the latest fetched watermark.
+                let latest_fetched_wm = self
+                    .latest_fetched_wm
+                    .lock()
+                    .expect("failed to acquire lock");
+                return std::cmp::min(
+                    *latest_fetched_wm,
+                    Watermark::from_timestamp_millis(oldest_window.end_time.timestamp_millis() - 1)
+                        .expect("failed to parse time"),
+                );
+            }
         }
-    }
 
-    /// insert_offset inserts the offset to the tracked offsets.
-    pub(crate) async fn insert_offset(&self, offset: Offset, watermark: Option<Watermark>) {
-        if let Offset::Int(offset) = offset {
-            self.sender
-                .send(ISBWaterMarkActorMessage::InsertOffset {
-                    offset,
-                    watermark: watermark.unwrap_or(
-                        Watermark::from_timestamp_millis(-1).expect("failed to parse time"),
-                    ),
-                })
-                .await
-                .unwrap_or_else(|e| {
-                    error!("Failed to send message: {:?}", e);
-                });
-        } else {
-            error!(?offset, "Invalid offset type, cannot insert offset");
-        }
+        // if window manager is not configured, we should use the lowest watermark among all the
+        // inflight messages.
+        self.get_lowest_watermark()
+            .await
+            .unwrap_or(Watermark::from_timestamp_millis(-1).unwrap())
     }
 
-    /// publishes the idle watermark for the downstream idle partitions.
-    pub(crate) async fn publish_idle_watermark(&self) {
-        self.sender
-            .send(ISBWaterMarkActorMessage::CheckAndPublishIdleWatermark)
-            .await
-            .unwrap_or_else(|e| {
-                error!("Failed to send message: {:?}", e);
-            });
+    /// Gets the lowest watermark among all the inflight requests
+    async fn get_lowest_watermark(&self) -> Option<Watermark> {
+        let offset_set = self.offset_set.lock().expect("failed to acquire lock");
+        offset_set
+            .values()
+            .filter_map(|set| set.iter().next().map(|ow| ow.watermark))
+            .min()
     }
 }
 
@@ -461,7 +537,7 @@ mod tests {
             to_vertex_config: vec![to_bucket_config.clone()],
         };
 
-        let handle = ISBWatermarkHandle::new(
+        let mut handle = ISBWatermarkHandle::new(
             vertex_name,
             0,
             Duration::from_millis(100),
@@ -477,6 +553,7 @@ mod tests {
                 conditions: None,
             }],
             CancellationToken::new(),
+            None,
         )
         .await
         .expect("Failed to create ISBWatermarkHandle");
@@ -641,7 +718,7 @@ mod tests {
             to_vertex_config: vec![from_bucket_config.clone()],
         };
 
-        let handle = ISBWatermarkHandle::new(
+        let mut handle = ISBWatermarkHandle::new(
             vertex_name,
             0,
             Duration::from_millis(100),
@@ -657,6 +734,7 @@ mod tests {
                 conditions: None,
             }],
             CancellationToken::new(),
+            None,
         )
         .await
         .expect("Failed to create ISBWatermarkHandle");
@@ -787,7 +865,7 @@ mod tests {
             to_vertex_config: vec![to_bucket_config.clone()],
         };
 
-        let handle = ISBWatermarkHandle::new(
+        let mut handle = ISBWatermarkHandle::new(
             vertex_name,
             0,
             Duration::from_millis(10), // Set idle timeout to a very short duration
@@ -803,6 +881,7 @@ mod tests {
                 conditions: None,
             }],
             CancellationToken::new(),
+            None,
         )
         .await
         .expect("Failed to create ISBWatermarkHandle");
diff --git a/rust/numaflow-core/src/watermark/processor/manager.rs b/rust/numaflow-core/src/watermark/processor/manager.rs
index 5f55f413..bb49556a 100644
--- a/rust/numaflow-core/src/watermark/processor/manager.rs
+++ b/rust/numaflow-core/src/watermark/processor/manager.rs
@@ -226,7 +226,7 @@ impl ProcessorManager {
                 .await
                 .unwrap_or_else(|_| Vec::new()),
             Err(e) => {
-                warn!(?e, "Failed to get keys from ot bucket");
+                warn!(error = ?e, "Failed to get keys from ot bucket");
                 Vec::new()
             }
         };
@@ -303,7 +303,7 @@ impl ProcessorManager {
             let kv = match val {
                 Ok(kv) => kv,
                 Err(e) => {
-                    warn!(?e, "Failed to get next kv entry, recreating watcher");
+                    warn!(error = ?e, "Failed to get next kv entry, recreating watcher");
                     ot_watcher = Self::create_watcher(ot_bucket.clone()).await;
                     continue;
                 }
@@ -353,7 +353,7 @@ impl ProcessorManager {
             let kv = match val {
                 Ok(kv) => kv,
                 Err(e) => {
-                    warn!(?e, "Failed to get next kv entry, recreating watcher");
+                    warn!(error = ?e, "Failed to get next kv entry, recreating watcher");
                     hb_watcher = Self::create_watcher(hb_bucket.clone()).await;
                     continue;
                 }
diff --git a/rust/numaflow-core/src/watermark/source.rs b/rust/numaflow-core/src/watermark/source.rs
index 1c31d7d8..c25f5128 100644
--- a/rust/numaflow-core/src/watermark/source.rs
+++ b/rust/numaflow-core/src/watermark/source.rs
@@ -16,8 +16,10 @@
 //! [ISB]: https://numaflow.numaproj.io/core-concepts/inter-step-buffer/
 
 use std::collections::HashMap;
+use std::sync::Arc;
 use std::time::Duration;
 
+use std::sync::Mutex;
 use tokio::sync::mpsc::Receiver;
 use tokio_util::sync::CancellationToken;
 use tracing::error;
@@ -26,12 +28,13 @@ use crate::config::pipeline::ToVertexConfig;
 use crate::config::pipeline::isb::Stream;
 use crate::config::pipeline::watermark::SourceWatermarkConfig;
 use crate::error::{Error, Result};
-use crate::message::{IntOffset, Message, Offset};
+use crate::message::{Message, Offset};
 use crate::watermark::idle::isb::ISBIdleDetector;
 use crate::watermark::idle::source::SourceIdleDetector;
 use crate::watermark::processor::manager::ProcessorManager;
 use crate::watermark::source::source_wm_fetcher::SourceWatermarkFetcher;
 use crate::watermark::source::source_wm_publisher::SourceWatermarkPublisher;
+use crate::watermark::wmb::Watermark;
 
 /// fetcher for fetching the source watermark
 pub(crate) mod source_wm_fetcher;
@@ -43,44 +46,33 @@ pub(crate) mod source_wm_publisher;
 #[allow(clippy::enum_variant_names)]
 enum SourceActorMessage {
     PublishSourceWatermark {
-        map: HashMap<u16, i64>,
+        partition: u16,
+        event_time: i64,
+        is_idle: bool,
     },
     PublishISBWatermark {
-        offset: IntOffset,
-        stream: Stream,
         input_partition: u16,
+        stream: Stream,
+        offset: i64,
+        watermark: i64,
+        is_idle: bool,
     },
-    CheckAndPublishSourceIdleWatermark {
-        /// partitions which are/were reading messages.
-        source_partitions: Vec<u16>,
+    FetchSourceWatermark {
+        oneshot_tx: tokio::sync::oneshot::Sender<Result<Watermark>>,
     },
-    CheckAndPublishISBIdleWatermark,
 }
 
 /// SourceWatermarkActor comprises SourcePublisher and SourceFetcher.
+/// Only responsible for the actual fetch and publish operations.
 struct SourceWatermarkActor {
     publisher: SourceWatermarkPublisher,
     fetcher: SourceWatermarkFetcher,
-    isb_idle_manager: ISBIdleDetector,
-    source_idle_manager: Option<SourceIdleDetector>,
-    active_input_partitions: HashMap<u16, bool>,
 }
 
 impl SourceWatermarkActor {
     /// Creates a new SourceWatermarkActor.
-    fn new(
-        publisher: SourceWatermarkPublisher,
-        fetcher: SourceWatermarkFetcher,
-        isb_idle_manager: ISBIdleDetector,
-        source_idle_manager: Option<SourceIdleDetector>,
-    ) -> Self {
-        Self {
-            publisher,
-            fetcher,
-            isb_idle_manager,
-            source_idle_manager,
-            active_input_partitions: HashMap::new(),
-        }
+    fn new(publisher: SourceWatermarkPublisher, fetcher: SourceWatermarkFetcher) -> Self {
+        Self { publisher, fetcher }
     }
 
     /// Runs the SourceWatermarkActor
@@ -96,139 +88,35 @@ impl SourceWatermarkActor {
     async fn handle_message(&mut self, message: SourceActorMessage) -> Result<()> {
         match message {
             // publish the watermark for the messages read from the source to the source bucket
-            // we consider the min event time that we have seen in the batch for each partition
-            SourceActorMessage::PublishSourceWatermark { map } => {
-                if map.is_empty() {
-                    return Ok(());
-                }
-                for (partition, event_time) in map {
-                    self.publisher
-                        .publish_source_watermark(partition, event_time, false)
-                        .await;
-                    // cache the active input partitions, we need it for publishing isb idle watermark
-                    self.active_input_partitions.insert(partition, true);
-                }
-                if let Some(source_idle_manager) = &mut self.source_idle_manager {
-                    source_idle_manager.reset();
-                }
+            SourceActorMessage::PublishSourceWatermark {
+                partition,
+                event_time,
+                is_idle,
+            } => {
+                self.publisher
+                    .publish_source_watermark(partition, event_time, is_idle)
+                    .await;
             }
 
             // publish the watermark for the messages written to ISB
             SourceActorMessage::PublishISBWatermark {
-                offset,
-                stream,
                 input_partition,
+                stream,
+                offset,
+                watermark,
+                is_idle,
             } => {
-                let watermark = self.fetcher.fetch_source_watermark();
                 self.publisher
-                    .publish_isb_watermark(
-                        input_partition,
-                        &stream,
-                        offset.offset,
-                        watermark.timestamp_millis(),
-                        false,
-                    )
+                    .publish_isb_watermark(input_partition, &stream, offset, watermark, is_idle)
                     .await;
-                // mark the vertex and partition as active since we published the watermark
-                self.isb_idle_manager.reset_idle(&stream).await;
-            }
-
-            // publish the idle watermark for the given partitions (when source is not reading any
-            // messages)
-            SourceActorMessage::CheckAndPublishSourceIdleWatermark { source_partitions } => {
-                let Some(source_idle_manager) = &mut self.source_idle_manager else {
-                    return Ok(());
-                };
-
-                // check if idling is enabled on source and all conditions are met
-                if !source_idle_manager.is_source_idling() {
-                    return Ok(());
-                }
-
-                // compute the watermark that should be published.
-                let compute_wm = self.fetcher.fetch_source_watermark();
-                // using the computed watermark, fetch the idle watermark.
-                let idle_wm =
-                    source_idle_manager.update_and_fetch_idle_wm(compute_wm.timestamp_millis());
-
-                // publish the idle watermark for the given partitions
-                for partition in source_partitions.iter() {
-                    self.publisher
-                        .publish_source_watermark(*partition, idle_wm, true)
-                        .await;
-                }
-
-                // since isb will also be idling since we are not reading any data
-                // we need to propagate idle watermarks to ISB
-                let compute_wm = self.fetcher.fetch_source_watermark();
-                if compute_wm.timestamp_millis() == -1 {
-                    return Ok(());
-                }
-
-                // all the isb partitions will be idling because the source is idling, fetch the idle offset
-                // for each vertex and partition and publish the idle watermark
-                let vertex_streams = self.isb_idle_manager.fetch_all_streams().await;
-                for stream in vertex_streams.iter() {
-                    let offset = self.isb_idle_manager.fetch_idle_offset(stream).await?;
-                    for idle_partition in source_partitions.iter() {
-                        self.publisher
-                            .publish_isb_watermark(
-                                *idle_partition,
-                                stream,
-                                offset,
-                                compute_wm.timestamp_millis(),
-                                true,
-                            )
-                            .await;
-                    }
-
-                    // mark the vertex and partition as idle, since we published the idle watermark
-                    self.isb_idle_manager
-                        .update_idle_metadata(stream, offset)
-                        .await;
-                }
             }
 
-            // publish the idle watermark for the ISB partitions, this will be invoked by the periodic
-            // task which keeps check every idle_timeout duration to see if there are any idle partitions
-            // branch idling case(cf)
-            SourceActorMessage::CheckAndPublishISBIdleWatermark => {
-                // if source is idling, we can avoid publishing the idle watermark since we publish
-                // the idle watermark for all the downstream partitions in the source idling control flow
-                if let Some(source_idle_manager) = &self.source_idle_manager {
-                    if source_idle_manager.is_source_idling() {
-                        return Ok(());
-                    }
-                }
-
-                // fetch the source watermark, identify the idle partitions and publish the idle watermark
-                let compute_wm = self.fetcher.fetch_source_watermark();
-                if compute_wm.timestamp_millis() == -1 {
-                    return Ok(());
-                }
-
-                // we should only publish to active input partitions, because we consider input-partitions as
-                // the processing entity while publishing watermark inside source
-                let idle_streams = self.isb_idle_manager.fetch_idle_streams().await;
-                for stream in idle_streams.iter() {
-                    let offset = self.isb_idle_manager.fetch_idle_offset(stream).await?;
-                    for partition in self.active_input_partitions.keys() {
-                        self.publisher
-                            .publish_isb_watermark(
-                                *partition,
-                                stream,
-                                offset,
-                                compute_wm.timestamp_millis(),
-                                true,
-                            )
-                            .await;
-                    }
-                    self.isb_idle_manager
-                        .update_idle_metadata(stream, offset)
-                        .await;
-                }
-                // clear the cache since we published the idle watermarks
-                self.active_input_partitions.clear();
+            // fetch the source watermark
+            SourceActorMessage::FetchSourceWatermark { oneshot_tx } => {
+                let watermark = self.fetcher.fetch_source_watermark();
+                oneshot_tx
+                    .send(Ok(watermark))
+                    .map_err(|_| Error::Watermark("failed to send response".to_string()))?;
             }
         }
 
@@ -238,9 +126,13 @@ impl SourceWatermarkActor {
 
 /// SourceWatermarkHandle is the handle for the SourceWatermarkActor.
 /// Exposes methods to publish the source watermark and edge watermark.
+/// Contains all the computation logic.
 #[derive(Clone)]
 pub(crate) struct SourceWatermarkHandle {
     sender: tokio::sync::mpsc::Sender<SourceActorMessage>,
+    isb_idle_manager: ISBIdleDetector,
+    source_idle_manager: Option<SourceIdleDetector>,
+    active_input_partitions: Arc<Mutex<HashMap<u16, bool>>>,
 }
 
 impl SourceWatermarkHandle {
@@ -274,15 +166,19 @@ impl SourceWatermarkHandle {
         let isb_idle_manager =
             ISBIdleDetector::new(idle_timeout, to_vertex_configs, js_context.clone()).await;
 
-        let actor =
-            SourceWatermarkActor::new(publisher, fetcher, isb_idle_manager, source_idle_manager);
+        let actor = SourceWatermarkActor::new(publisher, fetcher);
         tokio::spawn(async move { actor.run(receiver).await });
 
-        let source_watermark_handle = Self { sender };
+        let source_watermark_handle = Self {
+            sender,
+            isb_idle_manager,
+            source_idle_manager,
+            active_input_partitions: Arc::new(Mutex::new(HashMap::new())),
+        };
 
         // start a task to keep publishing idle watermarks every 100ms
         tokio::spawn({
-            let source_watermark_handle = source_watermark_handle.clone();
+            let mut source_watermark_handle = source_watermark_handle.clone();
             let mut interval_ticker = tokio::time::interval(idle_timeout);
             async move {
                 loop {
@@ -302,7 +198,7 @@ impl SourceWatermarkHandle {
     }
 
     /// Generates and Publishes the source watermark for the given messages.
-    pub(crate) async fn generate_and_publish_source_watermark(&self, messages: &[Message]) {
+    pub(crate) async fn generate_and_publish_source_watermark(&mut self, messages: &[Message]) {
         // we need to build a hash-map of the lowest event time for each partition
         let partition_to_lowest_event_time =
             messages.iter().fold(HashMap::new(), |mut acc, message| {
@@ -320,50 +216,213 @@ impl SourceWatermarkHandle {
                 acc
             });
 
-        self.sender
-            .send(SourceActorMessage::PublishSourceWatermark {
-                map: partition_to_lowest_event_time,
-            })
-            .await
-            .unwrap_or_else(|e| error!("failed to send message: {:?}", e));
+        if partition_to_lowest_event_time.is_empty() {
+            return;
+        }
+
+        // Publish the watermark for each partition
+        for (partition, event_time) in partition_to_lowest_event_time {
+            self.sender
+                .send(SourceActorMessage::PublishSourceWatermark {
+                    partition,
+                    event_time,
+                    is_idle: false,
+                })
+                .await
+                .unwrap_or_else(|e| error!("failed to send message: {:?}", e));
+
+            // cache the active input partitions, we need it for publishing isb idle watermark
+            let mut active_input_partitions = self
+                .active_input_partitions
+                .lock()
+                .expect("failed to acquire lock");
+            active_input_partitions.insert(partition, true);
+        }
+
+        // Reset the source idle manager
+        if let Some(source_idle_manager) = &mut self.source_idle_manager {
+            source_idle_manager.reset();
+        }
     }
 
     /// Publishes the watermark for the given input partition on to the ISB of the next vertex.
     pub(crate) async fn publish_source_isb_watermark(
-        &self,
+        &mut self,
         stream: Stream,
         offset: Offset,
         input_partition: u16,
     ) {
-        // the fetching happens in the handler
-        if let Offset::Int(offset) = offset {
-            self.sender
-                .send(SourceActorMessage::PublishISBWatermark {
-                    offset,
-                    stream,
-                    input_partition,
-                })
-                .await
-                .expect("failed to send message");
-        }
-    }
+        let Offset::Int(offset) = offset else {
+            error!(?offset, "Invalid offset type, cannot publish watermark");
+            return;
+        };
+
+        // Fetch the source watermark
+        let watermark = self.fetch_source_watermark().await;
 
-    pub(crate) async fn publish_source_idle_watermark(&self, partitions: Vec<u16>) {
+        // Send the publish watermark message to the actor
         self.sender
-            .send(SourceActorMessage::CheckAndPublishSourceIdleWatermark {
-                source_partitions: partitions,
+            .send(SourceActorMessage::PublishISBWatermark {
+                input_partition,
+                stream: stream.clone(),
+                offset: offset.offset,
+                watermark: watermark.timestamp_millis(),
+                is_idle: false,
             })
             .await
-            .map_err(|_| Error::Watermark("failed to send message".to_string()))
             .expect("failed to send message");
+
+        // Mark the vertex and partition as active since we published the watermark
+        self.isb_idle_manager.reset_idle(&stream).await;
     }
 
-    pub(crate) async fn publish_isb_idle_watermark(&self) {
-        self.sender
-            .send(SourceActorMessage::CheckAndPublishISBIdleWatermark)
+    /// Fetches the source watermark.
+    pub(crate) async fn fetch_source_watermark(&mut self) -> Watermark {
+        let (oneshot_tx, oneshot_rx) = tokio::sync::oneshot::channel();
+        if let Err(e) = self
+            .sender
+            .send(SourceActorMessage::FetchSourceWatermark { oneshot_tx })
             .await
-            .map_err(|_| Error::Watermark("failed to send message".to_string()))
-            .expect("failed to send message");
+        {
+            error!(?e, "Failed to send message");
+            return Watermark::from_timestamp_millis(-1).expect("failed to parse time");
+        }
+
+        match oneshot_rx.await {
+            Ok(watermark) => watermark.unwrap_or_else(|e| {
+                error!(?e, "Failed to fetch watermark");
+                Watermark::from_timestamp_millis(-1).expect("failed to parse time")
+            }),
+            Err(e) => {
+                error!(?e, "Failed to receive response");
+                Watermark::from_timestamp_millis(-1).expect("failed to parse time")
+            }
+        }
+    }
+
+    pub(crate) async fn publish_source_idle_watermark(&mut self, partitions: Vec<u16>) {
+        // First check if source idle manager exists and if source is idling
+        let is_source_idling = if let Some(source_idle_manager) = &self.source_idle_manager {
+            source_idle_manager.is_source_idling()
+        } else {
+            return;
+        };
+
+        if !is_source_idling {
+            return;
+        }
+
+        // Fetch the source watermark first
+        let compute_wm = self.fetch_source_watermark().await;
+
+        // Now get the idle watermark
+        let idle_wm = if let Some(source_idle_manager) = &mut self.source_idle_manager {
+            source_idle_manager.update_and_fetch_idle_wm(compute_wm.timestamp_millis())
+        } else {
+            return;
+        };
+
+        // publish the idle watermark for the given partitions
+        for partition in partitions.iter() {
+            self.sender
+                .send(SourceActorMessage::PublishSourceWatermark {
+                    partition: *partition,
+                    event_time: idle_wm,
+                    is_idle: true,
+                })
+                .await
+                .unwrap_or_else(|e| error!("failed to send message: {:?}", e));
+        }
+
+        // since isb will also be idling since we are not reading any data
+        // we need to propagate idle watermarks to ISB
+        let compute_wm = self.fetch_source_watermark().await;
+        if compute_wm.timestamp_millis() == -1 {
+            return;
+        }
+
+        // all the isb partitions will be idling because the source is idling, fetch the idle offset
+        // for each vertex and partition and publish the idle watermark
+        let vertex_streams = self.isb_idle_manager.fetch_all_streams().await;
+        for stream in vertex_streams.iter() {
+            let offset = self
+                .isb_idle_manager
+                .fetch_idle_offset(stream)
+                .await
+                .unwrap_or(-1);
+            for idle_partition in partitions.iter() {
+                self.sender
+                    .send(SourceActorMessage::PublishISBWatermark {
+                        input_partition: *idle_partition,
+                        stream: stream.clone(),
+                        offset,
+                        watermark: compute_wm.timestamp_millis(),
+                        is_idle: true,
+                    })
+                    .await
+                    .unwrap_or_else(|e| error!("failed to send message: {:?}", e));
+            }
+
+            // mark the vertex and partition as idle, since we published the idle watermark
+            self.isb_idle_manager
+                .update_idle_metadata(stream, offset)
+                .await;
+        }
+    }
+
+    pub(crate) async fn publish_isb_idle_watermark(&mut self) {
+        // if source is idling, we can avoid publishing the idle watermark since we publish
+        // the idle watermark for all the downstream partitions in the source idling control flow
+        if let Some(source_idle_manager) = &self.source_idle_manager {
+            if source_idle_manager.is_source_idling() {
+                return;
+            }
+        }
+
+        // fetch the source watermark, identify the idle partitions and publish the idle watermark
+        let compute_wm = self.fetch_source_watermark().await;
+        if compute_wm.timestamp_millis() == -1 {
+            return;
+        }
+
+        // we should only publish to active input partitions, because we consider input-partitions as
+        // the processing entity while publishing watermark inside source
+        let idle_streams = self.isb_idle_manager.fetch_idle_streams().await;
+        for stream in idle_streams.iter() {
+            let offset = self
+                .isb_idle_manager
+                .fetch_idle_offset(stream)
+                .await
+                .unwrap_or(-1);
+            let active_input_partitions = {
+                let active_input_partitions = self
+                    .active_input_partitions
+                    .lock()
+                    .expect("failed to acquire lock");
+                active_input_partitions.keys().cloned().collect::<Vec<_>>()
+            };
+            for partition in active_input_partitions {
+                self.sender
+                    .send(SourceActorMessage::PublishISBWatermark {
+                        input_partition: partition,
+                        stream: stream.clone(),
+                        offset,
+                        watermark: compute_wm.timestamp_millis(),
+                        is_idle: true,
+                    })
+                    .await
+                    .unwrap_or_else(|e| error!("failed to send message: {:?}", e));
+            }
+            self.isb_idle_manager
+                .update_idle_metadata(stream, offset)
+                .await;
+        }
+        // clear the cache since we published the idle watermarks
+        let mut active_input_partitions = self
+            .active_input_partitions
+            .lock()
+            .expect("failed to acquire lock");
+        active_input_partitions.clear();
     }
 }
 
@@ -424,7 +483,7 @@ mod tests {
             .await
             .unwrap();
 
-        let handle = SourceWatermarkHandle::new(
+        let mut handle = SourceWatermarkHandle::new(
             Duration::from_millis(100),
             js_context.clone(),
             Default::default(),
@@ -560,7 +619,7 @@ mod tests {
             .await
             .unwrap();
 
-        let handle = SourceWatermarkHandle::new(
+        let mut handle = SourceWatermarkHandle::new(
             Duration::from_millis(100),
             js_context.clone(),
             &[ToVertexConfig {
@@ -756,7 +815,7 @@ mod tests {
             increment_by: Duration::from_millis(1),
         };
 
-        let handle = SourceWatermarkHandle::new(
+        let mut handle = SourceWatermarkHandle::new(
             Duration::from_millis(10),
             js_context.clone(),
             &to_vertex_configs,
@@ -947,7 +1006,7 @@ mod tests {
             increment_by: Duration::from_millis(1),
         };
 
-        let handle = SourceWatermarkHandle::new(
+        let mut handle = SourceWatermarkHandle::new(
             Duration::from_millis(5),
             js_context.clone(),
             &to_vertex_configs,
diff --git a/rust/numaflow-models/Cargo.toml b/rust/numaflow-models/Cargo.toml
index ebd46325..f4144bb2 100644
--- a/rust/numaflow-models/Cargo.toml
+++ b/rust/numaflow-models/Cargo.toml
@@ -13,7 +13,7 @@ serde = "^1.0"
 serde_derive = "^1.0"
 serde_json = "^1.0"
 url = "^2.2"
-uuid = { version = "^1.0", features = ["serde", "v4"] }
+uuid = { workspace = true, features = ["serde", "v4"] }
 [dependencies.reqwest]
 version = "^0.11"
 default-features = false
diff --git a/rust/numaflow-models/src/models/abstract_sink.rs b/rust/numaflow-models/src/models/abstract_sink.rs
index 3a650c22..5c513bec 100644
--- a/rust/numaflow-models/src/models/abstract_sink.rs
+++ b/rust/numaflow-models/src/models/abstract_sink.rs
@@ -26,6 +26,8 @@ pub struct AbstractSink {
     pub log: Option<Box<crate::models::Log>>,
     #[serde(rename = "serve", skip_serializing_if = "Option::is_none")]
     pub serve: Option<Box<crate::models::ServeSink>>,
+    #[serde(rename = "sqs", skip_serializing_if = "Option::is_none")]
+    pub sqs: Option<Box<crate::models::SqsSink>>,
     #[serde(rename = "udsink", skip_serializing_if = "Option::is_none")]
     pub udsink: Option<Box<crate::models::UdSink>>,
 }
@@ -37,6 +39,7 @@ impl AbstractSink {
             kafka: None,
             log: None,
             serve: None,
+            sqs: None,
             udsink: None,
         }
     }
diff --git a/rust/numaflow-models/src/models/backoff.rs b/rust/numaflow-models/src/models/backoff.rs
index 7d2baddc..09d95882 100644
--- a/rust/numaflow-models/src/models/backoff.rs
+++ b/rust/numaflow-models/src/models/backoff.rs
@@ -20,9 +20,17 @@ limitations under the License.
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct Backoff {
+    #[serde(rename = "cap", skip_serializing_if = "Option::is_none")]
+    pub cap: Option<kube::core::Duration>,
+    /// Interval is multiplied by factor each iteration, if factor is not zero and the limits imposed by Steps and Cap have not been reached.
+    #[serde(rename = "factor", skip_serializing_if = "Option::is_none")]
+    pub factor: Option<f64>,
     #[serde(rename = "interval", skip_serializing_if = "Option::is_none")]
     pub interval: Option<kube::core::Duration>,
-    /// Steps defines the number of times to try writing to a sink including retries
+    /// The sleep at each iteration is the interval plus an additional amount chosen uniformly at random from the interval between zero and `jitter*interval`.
+    #[serde(rename = "jitter", skip_serializing_if = "Option::is_none")]
+    pub jitter: Option<f64>,
+    /// Steps defines the maximum number of retry attempts
     #[serde(rename = "steps", skip_serializing_if = "Option::is_none")]
     pub steps: Option<i64>,
 }
@@ -31,7 +39,10 @@ impl Backoff {
     /// Backoff defines parameters used to systematically configure the retry strategy.
     pub fn new() -> Backoff {
         Backoff {
+            cap: None,
+            factor: None,
             interval: None,
+            jitter: None,
             steps: None,
         }
     }
diff --git a/rust/numaflow-models/src/models/mod.rs b/rust/numaflow-models/src/models/mod.rs
index 3c50a039..7ecb3606 100644
--- a/rust/numaflow-models/src/models/mod.rs
+++ b/rust/numaflow-models/src/models/mod.rs
@@ -180,6 +180,8 @@ pub mod sliding_window;
 pub use self::sliding_window::SlidingWindow;
 pub mod source;
 pub use self::source::Source;
+pub mod sqs_sink;
+pub use self::sqs_sink::SqsSink;
 pub mod sqs_source;
 pub use self::sqs_source::SqsSource;
 pub mod status;
diff --git a/rust/numaflow-models/src/models/retry_strategy.rs b/rust/numaflow-models/src/models/retry_strategy.rs
index 22cfc480..9c9078ab 100644
--- a/rust/numaflow-models/src/models/retry_strategy.rs
+++ b/rust/numaflow-models/src/models/retry_strategy.rs
@@ -16,7 +16,7 @@ limitations under the License.
 
 // Code generated by Openapi Generator. DO NOT EDIT.
 
-/// RetryStrategy : RetryStrategy struct encapsulates the settings for retrying operations in the event of failures. It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.
+/// RetryStrategy : The RetryStrategy struct defines the configuration for handling operation retries in case of failures. It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
 pub struct RetryStrategy {
@@ -28,7 +28,7 @@ pub struct RetryStrategy {
 }
 
 impl RetryStrategy {
-    /// RetryStrategy struct encapsulates the settings for retrying operations in the event of failures. It includes a BackOff strategy to manage the timing of retries and defines the action to take upon failure.
+    /// The RetryStrategy struct defines the configuration for handling operation retries in case of failures. It incorporates an Exponential BackOff strategy to control retry timing and specifies the actions to take upon failure.
     pub fn new() -> RetryStrategy {
         RetryStrategy {
             backoff: None,
diff --git a/rust/numaflow-models/src/models/sink.rs b/rust/numaflow-models/src/models/sink.rs
index 2f561969..2721e980 100644
--- a/rust/numaflow-models/src/models/sink.rs
+++ b/rust/numaflow-models/src/models/sink.rs
@@ -30,6 +30,8 @@ pub struct Sink {
     pub retry_strategy: Option<Box<crate::models::RetryStrategy>>,
     #[serde(rename = "serve", skip_serializing_if = "Option::is_none")]
     pub serve: Option<Box<crate::models::ServeSink>>,
+    #[serde(rename = "sqs", skip_serializing_if = "Option::is_none")]
+    pub sqs: Option<Box<crate::models::SqsSink>>,
     #[serde(rename = "udsink", skip_serializing_if = "Option::is_none")]
     pub udsink: Option<Box<crate::models::UdSink>>,
 }
@@ -43,6 +45,7 @@ impl Sink {
             log: None,
             retry_strategy: None,
             serve: None,
+            sqs: None,
             udsink: None,
         }
     }
diff --git a/rust/numaflow-models/src/models/sqs_sink.rs b/rust/numaflow-models/src/models/sqs_sink.rs
new file mode 100644
index 00000000..0e24a7f6
--- /dev/null
+++ b/rust/numaflow-models/src/models/sqs_sink.rs
@@ -0,0 +1,44 @@
+/*
+Copyright 2022 The Numaproj Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+// Code generated by Openapi Generator. DO NOT EDIT.
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
+pub struct SqsSink {
+    /// AWSRegion is the AWS Region where the SQS queue is located
+    #[serde(rename = "awsRegion")]
+    pub aws_region: String,
+    /// QueueName is the name of the SQS queue
+    #[serde(rename = "queueName")]
+    pub queue_name: String,
+    /// QueueOwnerAWSAccountID is the queue owner aws account id
+    #[serde(rename = "queueOwnerAWSAccountID")]
+    pub queue_owner_aws_account_id: String,
+}
+
+impl SqsSink {
+    pub fn new(
+        aws_region: String,
+        queue_name: String,
+        queue_owner_aws_account_id: String,
+    ) -> SqsSink {
+        SqsSink {
+            aws_region,
+            queue_name,
+            queue_owner_aws_account_id,
+        }
+    }
+}
diff --git a/rust/numaflow-models/templates/Cargo.mustache b/rust/numaflow-models/templates/Cargo.mustache
index ebd46325..f4144bb2 100644
--- a/rust/numaflow-models/templates/Cargo.mustache
+++ b/rust/numaflow-models/templates/Cargo.mustache
@@ -13,7 +13,7 @@ serde = "^1.0"
 serde_derive = "^1.0"
 serde_json = "^1.0"
 url = "^2.2"
-uuid = { version = "^1.0", features = ["serde", "v4"] }
+uuid = { workspace = true, features = ["serde", "v4"] }
 [dependencies.reqwest]
 version = "^0.11"
 default-features = false
diff --git a/rust/numaflow-pb/src/clients/reduce.v1.rs b/rust/numaflow-pb/src/clients/reduce.v1.rs
index 22c98a00..f41efd2a 100644
--- a/rust/numaflow-pb/src/clients/reduce.v1.rs
+++ b/rust/numaflow-pb/src/clients/reduce.v1.rs
@@ -1,5 +1,4 @@
 // This file is @generated by prost-build.
-/// *
 /// ReduceRequest represents a request element.
 #[derive(Clone, PartialEq, ::prost::Message)]
 pub struct ReduceRequest {
@@ -90,7 +89,6 @@ pub struct Window {
     #[prost(string, tag = "3")]
     pub slot: ::prost::alloc::string::String,
 }
-/// *
 /// ReduceResponse represents a response element.
 #[derive(Clone, PartialEq, ::prost::Message)]
 pub struct ReduceResponse {
@@ -116,7 +114,6 @@ pub mod reduce_response {
         pub tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
     }
 }
-/// *
 /// ReadyResponse is the health check result.
 #[derive(Clone, Copy, PartialEq, ::prost::Message)]
 pub struct ReadyResponse {
diff --git a/rust/numaflow-pb/src/clients/sessionreduce.v1.rs b/rust/numaflow-pb/src/clients/sessionreduce.v1.rs
index 784b47f4..20ffd6e7 100644
--- a/rust/numaflow-pb/src/clients/sessionreduce.v1.rs
+++ b/rust/numaflow-pb/src/clients/sessionreduce.v1.rs
@@ -12,7 +12,6 @@ pub struct KeyedWindow {
     #[prost(string, repeated, tag = "4")]
     pub keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
 }
-/// *
 /// SessionReduceRequest represents a request element.
 #[derive(Clone, PartialEq, ::prost::Message)]
 pub struct SessionReduceRequest {
@@ -98,7 +97,6 @@ pub mod session_reduce_request {
         >,
     }
 }
-/// *
 /// SessionReduceResponse represents a response element.
 #[derive(Clone, PartialEq, ::prost::Message)]
 pub struct SessionReduceResponse {
@@ -124,7 +122,6 @@ pub mod session_reduce_response {
         pub tags: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
     }
 }
-/// *
 /// ReadyResponse is the health check result.
 #[derive(Clone, Copy, PartialEq, ::prost::Message)]
 pub struct ReadyResponse {
diff --git a/rust/numaflow-pb/src/objects/wal.rs b/rust/numaflow-pb/src/objects/wal.rs
index 8c0a3474..c2832474 100644
--- a/rust/numaflow-pb/src/objects/wal.rs
+++ b/rust/numaflow-pb/src/objects/wal.rs
@@ -12,6 +12,26 @@ pub struct GcEvent {
     #[prost(string, repeated, tag = "3")]
     pub keys: ::prost::alloc::vec::Vec<::prost::alloc::string::String>,
 }
+/// Window represents an aligned window.
+#[derive(Clone, Copy, PartialEq, ::prost::Message)]
+pub struct Window {
+    /// start_time of the window
+    #[prost(message, optional, tag = "1")]
+    pub start_time: ::core::option::Option<::prost_types::Timestamp>,
+    /// end time of the window
+    #[prost(message, optional, tag = "2")]
+    pub end_time: ::core::option::Option<::prost_types::Timestamp>,
+}
+/// WindowManagerState is the state of the window manager
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct WindowManagerState {
+    /// list of active windows
+    #[prost(message, repeated, tag = "1")]
+    pub windows: ::prost::alloc::vec::Vec<Window>,
+    /// we use int64 for the end time because it will be -1 if there are no windows
+    #[prost(int64, tag = "2")]
+    pub max_deleted_window_end_time: i64,
+}
 #[derive(Clone, Copy, PartialEq, ::prost::Message)]
 pub struct Footer {
     /// the latest event time in the current Segment
diff --git a/rust/numaflow/src/main.rs b/rust/numaflow/src/main.rs
index dc680b07..ed3cd1db 100644
--- a/rust/numaflow/src/main.rs
+++ b/rust/numaflow/src/main.rs
@@ -1,7 +1,6 @@
 use std::collections::HashMap;
 use std::env;
 use std::error::Error;
-use std::time::Duration;
 use tracing::{error, info};
 use tracing_subscriber::layer::SubscriberExt;
 use tracing_subscriber::util::SubscriberInitExt;
@@ -29,8 +28,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
 
     if let Err(e) = run().await {
         error!("{e:?}");
-        tracing::warn!("Sleeping after error");
-        tokio::time::sleep(Duration::from_secs(300)).await;
         return Err(e);
     }
     info!("Exiting...");
diff --git a/rust/serving/Cargo.toml b/rust/serving/Cargo.toml
index 2c8c787c..f5d5f17d 100644
--- a/rust/serving/Cargo.toml
+++ b/rust/serving/Cargo.toml
@@ -26,7 +26,7 @@ axum-macros = "0.5.0"
 hyper-util = { version = "0.1.6", features = ["client-legacy"] }
 serde_json = "1.0.120"
 tower-http = { version = "0.6.2", features = ["trace", "timeout"] }
-uuid = { version = "1.10.0", features = ["std", "v7"] }
+uuid = { workspace = true, features = ["std", "v7"] }
 trait-variant = "0.1.2"
 chrono = { version = "0.4", features = ["serde"] }
 base64 = "0.22.1"
diff --git a/rust/serving/src/app.rs b/rust/serving/src/app.rs
index f37d1586..ac1879e8 100644
--- a/rust/serving/src/app.rs
+++ b/rust/serving/src/app.rs
@@ -163,6 +163,8 @@ where
             // requests don't hang forever.
             TimeoutLayer::new(Duration::from_secs(app.settings.drain_timeout_secs)),
         )
+        // add early validations
+        .layer(middleware::from_fn(validate_request))
         // Add auth middleware to all user facing routes
         .layer(middleware::from_fn_with_state(
             app.settings.api_auth_token.clone(),
@@ -199,8 +201,30 @@ async fn graceful_shutdown(handle: Handle, duration_secs: u64) {
 
 const PUBLISH_ENDPOINTS: [&str; 3] = ["/v1/process/sync", "/v1/process/async", "/v1/process/fetch"];
 
-// auth middleware to do token based authentication for all user facing routes
-// if auth is enabled.
+/// validate the request before passing it to the handler
+pub(crate) async fn validate_request(request: axum::extract::Request, next: Next) -> Response {
+    // check if the request id contains "."
+    if let Some(header) = request.headers().get("X-Numaflow-Id") {
+        // make sure value does not contain "."
+        if header
+            .to_str()
+            .expect("header should be a string")
+            .contains(".")
+        {
+            return Response::builder()
+                .status(StatusCode::BAD_REQUEST)
+                .body(Body::from(format!(
+                    "Header-ID should not contain '.', found {}",
+                    header.to_str().expect("header should be a string")
+                )))
+                .expect("failed to build response");
+        }
+    };
+
+    next.run(request).await
+}
+
+/// auth middleware to do token based authentication for all user facing routes if auth is enabled.
 async fn auth_middleware(
     State(api_auth_token): State<Option<String>>,
     request: axum::extract::Request,
@@ -382,6 +406,68 @@ mod tests {
         Ok(())
     }
 
+    #[cfg(feature = "nats-tests")]
+    #[tokio::test]
+    async fn test_validate_request() -> Result<()> {
+        let settings = Settings {
+            api_auth_token: Some("test-token".into()),
+            ..Default::default()
+        };
+
+        let datum_store = InMemoryDataStore::new(None);
+        let callback_store = InMemoryCallbackStore::new(None);
+        let store_name = "test_validate_request";
+        let js_url = "localhost:4222";
+        let client = async_nats::connect(js_url).await.unwrap();
+        let context = jetstream::new(client);
+        let _ = context.delete_key_value(store_name).await;
+
+        let _ = context
+            .create_key_value(jetstream::kv::Config {
+                bucket: store_name.to_string(),
+                history: 5,
+                ..Default::default()
+            })
+            .await
+            .unwrap();
+
+        let status_tracker = StatusTracker::new(context.clone(), store_name, "0", None)
+            .await
+            .unwrap();
+
+        let pipeline_spec = PIPELINE_SPEC_ENCODED.parse().unwrap();
+        let msg_graph = MessageGraph::from_pipeline(&pipeline_spec)?;
+        let callback_state =
+            CallbackState::new(msg_graph, datum_store, callback_store, status_tracker).await?;
+
+        let nats_connection = async_nats::connect("localhost:4222")
+            .await
+            .expect("Failed to establish Jetstream connection");
+        let js_context = jetstream::new(nats_connection);
+
+        let app_state = AppState {
+            js_context,
+            settings: Arc::new(settings),
+            orchestrator_state: callback_state,
+            cancellation_token: CancellationToken::new(),
+        };
+
+        let router = router_with_auth(app_state).await.unwrap();
+        let res = router
+            .oneshot(
+                axum::extract::Request::builder()
+                    .method("POST")
+                    .uri("/v1/process/sync")
+                    .header("X-Numaflow-Id", "test.id")
+                    .body(Body::empty())
+                    .unwrap(),
+            )
+            .await?;
+
+        assert_eq!(res.status(), StatusCode::BAD_REQUEST);
+        Ok(())
+    }
+
     #[cfg(feature = "nats-tests")]
     #[tokio::test]
     async fn test_auth_middleware() -> Result<()> {
diff --git a/rust/serving/src/app/orchestrator.rs b/rust/serving/src/app/orchestrator.rs
index c099ee6f..a7e901f9 100644
--- a/rust/serving/src/app/orchestrator.rs
+++ b/rust/serving/src/app/orchestrator.rs
@@ -70,13 +70,13 @@ where
                     previous_pod_hash,
                 } => {
                     warn!(
-                        ?err,
+                        error = %err,
                         "Request was cancelled, fetching callbacks and responses again"
                     );
                     Some(previous_pod_hash)
                 }
                 status::Error::Duplicate(msg) => {
-                    warn!(error = ?msg, "Request already exists in the store");
+                    warn!(error = %msg, "Request already exists in the store");
                     serving_metrics().request_register_duplicate_count.inc();
                     return Err(Error::Duplicate(msg));
                 }
@@ -149,7 +149,8 @@ where
             }
             ProcessingStatus::Failed { error, pod_hash } => {
                 warn!(
-                    "Request was failed because of {error}, processing and fetching the responses again"
+                    %error,
+                    "Request was failed, processing and fetching the responses again"
                 );
                 let notify = self.process_request(id, request_type).await?;
                 notify.await.expect("sender was dropped")?;
@@ -184,13 +185,13 @@ where
                     previous_pod_hash,
                 } => {
                     warn!(
-                        ?err,
+                        error = %err,
                         "Request was cancelled, fetching callbacks and responses again"
                     );
                     Some(previous_pod_hash)
                 }
                 status::Error::Duplicate(msg) => {
-                    warn!(error = ?msg, "Request already exists in the tracker");
+                    warn!(error = %msg, "Request already exists in the tracker");
                     serving_metrics().request_register_duplicate_count.inc();
                     return Err(Error::Duplicate(msg));
                 }
diff --git a/rust/serving/src/app/store/cbstore/jetstream_store.rs b/rust/serving/src/app/store/cbstore/jetstream_store.rs
index 8f425664..4833c3ea 100644
--- a/rust/serving/src/app/store/cbstore/jetstream_store.rs
+++ b/rust/serving/src/app/store/cbstore/jetstream_store.rs
@@ -208,7 +208,10 @@ impl JetStreamCallbackStore {
                         Ok(callback_obj) => {
                             let callback = Arc::new(callback_obj);
                             if tx.send(callback).await.is_err() {
-                                warn!(request_id = ?id, "Receiver dropped during historical send. Stopping scan stream.");
+                                warn!(
+                                    request_id = %id,
+                                    "Receiver dropped during historical send. Stopping scan stream."
+                                );
                                 break;
                             }
                         }
diff --git a/rust/serving/src/app/store/datastore/jetstream.rs b/rust/serving/src/app/store/datastore/jetstream.rs
index 8d3ee49d..68affd5d 100644
--- a/rust/serving/src/app/store/datastore/jetstream.rs
+++ b/rust/serving/src/app/store/datastore/jetstream.rs
@@ -352,7 +352,7 @@ impl JetStreamDataStore {
                         });
                 }
                 Ok(None) => {
-                    warn!(?id, "No final result key found in KV store, retrying...");
+                    warn!(%id, "No final result key found in KV store, retrying...");
                     tokio::time::sleep(Duration::from_millis(5)).await;
                 }
                 Err(e) => {
diff --git a/rust/serving/src/app/store/datastore/user_defined.rs b/rust/serving/src/app/store/datastore/user_defined.rs
index b02623a9..571ccb33 100644
--- a/rust/serving/src/app/store/datastore/user_defined.rs
+++ b/rust/serving/src/app/store/datastore/user_defined.rs
@@ -75,7 +75,7 @@ pub(crate) async fn create_rpc_channel(socket_path: PathBuf) -> StoreResult<Chan
         async || match connect_with_uds(socket_path.clone()).await {
             Ok(channel) => Ok(channel),
             Err(e) => {
-                warn!(?e, ?socket_path, "Failed to connect to UDS socket");
+                warn!(error = ?e, ?socket_path, "Failed to connect to UDS socket");
                 Err(StoreError::Connection(format!(
                     "Failed to connect to uds socket {socket_path:?}: {:?}",
                     e
diff --git a/rust/serving/src/app/store/status.rs b/rust/serving/src/app/store/status.rs
index 724c0255..b1fa5d5f 100644
--- a/rust/serving/src/app/store/status.rs
+++ b/rust/serving/src/app/store/status.rs
@@ -158,7 +158,7 @@ impl StatusTracker {
                 Ok(())
             }
             Err(e) if e.kind() == CreateErrorKind::AlreadyExists => {
-                warn!(id, status_key, "Request ID already exists.");
+                warn!(%id, %status_key, "Request ID already exists.");
                 let existing_status = self.get_status(&status_key).await?;
                 self.handle_existing_status(id, &status_key, existing_status)
             }
@@ -206,14 +206,14 @@ impl StatusTracker {
         match existing_status {
             ProcessingStatus::InProgress { pod_hash } => {
                 if pod_hash == self.pod_hash {
-                    warn!(id, status_key, "Request is getting processed");
+                    warn!(%id, %status_key, "Request is getting processed");
                     Err(Error::Duplicate(id.to_string()))
                 } else {
                     // TODO: There can be a case where the pod terminates without updating the request
                     //  status(SIGKILL). Return with a body asking for retrying with a different ID.
                     warn!(
-                        id,
-                        status_key, "Request is getting processed in another pod"
+                        %id,
+                        %status_key, "Request is getting processed in another pod"
                     );
                     Err(Error::Other(
                         "Request is getting processed in another pod".to_string(),
@@ -222,13 +222,13 @@ impl StatusTracker {
             }
             ProcessingStatus::Completed { .. } => {
                 warn!(
-                    id,
-                    status_key, "Request already completed, returning as duplicate."
+                    %id,
+                    %status_key, "Request already completed, returning as duplicate."
                 );
                 Err(Error::Duplicate(id.to_string()))
             }
             ProcessingStatus::Failed { error, pod_hash } => {
-                warn!(id, status_key, error = ?error, "Request had failed, retrying...");
+                warn!(%id, %status_key, %error, "Request had failed, retrying...");
                 Err(Error::Cancelled {
                     err: error,
                     previous_pod_hash: pod_hash,
diff --git a/rust/serving/src/callback.rs b/rust/serving/src/callback.rs
index a0763141..6bd5afd7 100644
--- a/rust/serving/src/callback.rs
+++ b/rust/serving/src/callback.rs
@@ -116,7 +116,7 @@ impl CallbackHandler {
                 async || match store.put(&callbacks_key, Bytes::from(value.clone())).await {
                     Ok(resp) => Ok(resp),
                     Err(e) => {
-                        warn!(?e, "Failed to write callback to store, retrying..");
+                        warn!(error = ?e, "Failed to write callback to store, retrying..");
                         Err(Error::Other(format!(
                             "Failed to write callback to store: {}",
                             e
diff --git a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/Errors/partials/CollapsableError/index.tsx b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/Errors/partials/CollapsableError/index.tsx
index 08c36341..e1efb508 100644
--- a/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/Errors/partials/CollapsableError/index.tsx
+++ b/ui/src/components/common/SlidingSidebar/partials/VertexDetails/partials/Errors/partials/CollapsableError/index.tsx
@@ -24,7 +24,13 @@ const cleanText = (text: string) => {
 
 const highlightFilePaths = (rawText: string) => {
   const text = cleanText(rawText);
-  const filePathRegex = /((?:\/[^\s]+)+\.[a-zA-Z0-9:]+)|(\bat\s+[^\n]+)/g;
+  // Note: this regex may not cover all edge cases
+  // but it should work for most common cases
+  // It matches:
+  // - URLs (http/https) with or without anchors
+  // - file paths (e.g. /path/to/file) with or without extensions
+  // - lines starting with "at" (e.g. stack traces)
+  const filePathRegex = /(https?:\/\/[^\s]+(?:#[^\s]+)?)|((?:\/[^\s]+)+)|(\bat\s+[^\n]+)/g;
   const exclusionList = ["/google.rpc.DebugInfo", "/debug.Stack"];
 
   return text.split(filePathRegex).map((part, index) => {



Test patch:
diff --git a/pkg/reconciler/validator/pipeline_validate_test.go b/pkg/reconciler/validator/pipeline_validate_test.go
index f510ce88..039ecbab 100644
--- a/pkg/reconciler/validator/pipeline_validate_test.go
+++ b/pkg/reconciler/validator/pipeline_validate_test.go
@@ -520,6 +520,17 @@ func TestValidatePipeline(t *testing.T) {
 		assert.NoError(t, err)
 	})
 
+	t.Run("no tag values conditional forwarding", func(t *testing.T) {
+		testObj := testPipeline.DeepCopy()
+		operatorOr := dfv1.LogicOperatorOr
+		testObj.Spec.Edges[1].Conditions = &dfv1.ForwardConditions{Tags: &dfv1.TagConditions{
+			Operator: &operatorOr,
+			Values:   []string{}}}
+		err := ValidatePipeline(testObj)
+		assert.Error(t, err)
+		assert.Contains(t, err.Error(), "invalid edge: conditional forwarding requires at least one tag value")
+	})
+
 	t.Run("allow conditional forwarding from source vertex or udf vertex", func(t *testing.T) {
 		testObj := testPipeline.DeepCopy()
 		operatorOr := dfv1.LogicOperatorOr
@@ -1194,12 +1205,112 @@ func TestValidateSink(t *testing.T) {
 
 func TestIsValidSinkRetryStrategy(t *testing.T) {
 	zeroSteps := uint32(0)
+	invalidFactor := float64(0.5)
+	negativeJitter := float64(-0.5)
+	greaterThanOneJitter := float64(1.1)
+	validJitter := float64(0.5)
+	validFactor := float64(2.0)
+	validInterval := metav1.Duration{Duration: 1 * time.Millisecond}
+	validCap := metav1.Duration{Duration: 10 * time.Millisecond}
+	invalidCap := metav1.Duration{Duration: 500 * time.Microsecond}
 	tests := []struct {
 		name     string
 		sink     dfv1.Sink
 		strategy dfv1.RetryStrategy
 		wantErr  bool
 	}{
+		{
+			name: "valid strategy with cap >= interval",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Interval: &validInterval,
+					Cap:      &validCap,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "invalid strategy with cap < interval",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Interval: &validInterval,
+					Cap:      &invalidCap,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "valid strategy with jitter >= 0 and less than 1",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Jitter: &validJitter,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "invalid strategy with jitter < 0",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Jitter: &negativeJitter,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid strategy with jitter >= 1",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Jitter: &greaterThanOneJitter,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "invalid strategy with factor less than 1",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Factor: &invalidFactor,
+				},
+			},
+			wantErr: true,
+		},
+		{
+			name: "valid strategy with positive factor",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Factor: &validFactor,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "valid strategy with cap but no interval",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Cap: &validCap,
+				},
+			},
+			wantErr: false,
+		},
+		{
+			name: "invalid strategy with cap < default interval",
+			sink: dfv1.Sink{},
+			strategy: dfv1.RetryStrategy{
+				BackOff: &dfv1.Backoff{
+					Cap: &invalidCap,
+				},
+			},
+			wantErr: true,
+		},
 		{
 			name: "valid strategy with fallback configured",
 			sink: dfv1.Sink{Fallback: &dfv1.AbstractSink{
@@ -1250,9 +1361,9 @@ func TestIsValidSinkRetryStrategy(t *testing.T) {
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
 			tt.sink.RetryStrategy = tt.strategy
-			ok := hasValidSinkRetryStrategy(tt.sink)
-			if (!ok) != tt.wantErr {
-				t.Errorf("isValidSinkRetryStrategy() got = %v, want %v", ok, tt.wantErr)
+			err := hasValidSinkRetryStrategy(tt.sink)
+			if (err == nil) == tt.wantErr {
+				t.Errorf("isValidSinkRetryStrategy() got = %v, want %v", (err == nil), tt.wantErr)
 			}
 		})
 	}