diff --git a/Examples.AspNet/Examples.AspNet.csproj b/Examples.AspNet/Examples.AspNet.csproj
new file mode 100644
index 0000000..a1c7f27
--- /dev/null
+++ b/Examples.AspNet/Examples.AspNet.csproj
@@ -0,0 +1,15 @@
+
+
+
+ net11.0
+ enable
+ enable
+ true
+ Examples.AspNet
+
+
+
+
+
+
+
diff --git a/Examples.AspNet/Program.cs b/Examples.AspNet/Program.cs
new file mode 100644
index 0000000..8b8705c
--- /dev/null
+++ b/Examples.AspNet/Program.cs
@@ -0,0 +1,42 @@
+using ioxide.Kestrel;
+using Microsoft.Extensions.Logging;
+
+// A minimal ASP.NET Core app that runs on either the ioxide io_uring transport or Kestrel's stock
+// sockets transport. Pick with the TRANSPORT environment variable (default: ioxide):
+//
+// TRANSPORT=ioxide dotnet run # ioxide.Kestrel transport (io_uring, one reactor per core)
+// TRANSPORT=sockets dotnet run # stock Kestrel sockets transport (the framework default)
+//
+// Then: curl http://localhost:8080/ and curl http://localhost:8080/plaintext
+
+var builder = WebApplication.CreateBuilder(args);
+
+builder.Logging.ClearProviders(); // turn off all logging (no per-request info: lines)
+
+var transport = (Environment.GetEnvironmentVariable("TRANSPORT") ?? "ioxide").Trim().ToLowerInvariant();
+
+builder.WebHost.ConfigureKestrel(o => o.ListenAnyIP(8080));
+
+switch (transport)
+{
+ case "ioxide":
+ builder.WebHost.UseIoxide(o => o.ReactorCount = 16); // io_uring transport, 16 reactors (one ring per thread)
+ break;
+
+ case "sockets":
+ case "kestrel":
+ // Stock Kestrel sockets transport — the framework default, nothing to wire up.
+ break;
+
+ default:
+ Console.Error.WriteLine($"Unknown TRANSPORT '{transport}'. Use 'ioxide' or 'sockets'.");
+ return;
+}
+
+var app = builder.Build();
+
+app.MapGet("/", () => $"Hello from ioxide.Kestrel! transport={transport}");
+app.MapGet("/plaintext", () => "Hello, World!");
+
+Console.WriteLine($"[Examples.AspNet] listening on http://localhost:8080 (transport={transport})");
+app.Run();
diff --git a/ioxide.Kestrel/HopDuplexPipe.cs b/ioxide.Kestrel/HopDuplexPipe.cs
new file mode 100644
index 0000000..1397a5f
--- /dev/null
+++ b/ioxide.Kestrel/HopDuplexPipe.cs
@@ -0,0 +1,152 @@
+using System.Buffers;
+using System.IO.Pipelines;
+using ioxide;
+using ioxide.utils;
+
+namespace ioxide.Kestrel;
+
+///
+/// A Kestrel transport duplex over an ioxide : two BCL s whose
+/// reader schedulers route to the reactor thread (via ), plus a
+/// recv→inbound pump and an outbound→send pump that run on the reactor. This pins Kestrel's whole request
+/// loop to the reactor thread. One copy each way (recv bytes into the inbound pipe; response bytes into
+/// the connection slab).
+///
+internal sealed class HopDuplexPipe : IDuplexPipe, IAsyncDisposable
+{
+ private readonly Connection _conn;
+ private readonly Reactor _reactor;
+ private readonly Pipe _inbound; // recv pump writes; Kestrel reads (Transport.Input)
+ private readonly Pipe _outbound; // Kestrel writes (Transport.Output); send pump reads
+
+ private Task _recvPump = Task.CompletedTask;
+ private Task _sendPump = Task.CompletedTask;
+ private int _started;
+
+ public PipeReader Input => _inbound.Reader;
+ public PipeWriter Output => _outbound.Writer;
+
+ public HopDuplexPipe(Connection conn, Reactor reactor)
+ {
+ _conn = conn;
+ _reactor = reactor;
+ var scheduler = new ReactorPipeScheduler(reactor);
+
+ // Reader schedulers = the reactor: Kestrel's HTTP parse (inbound reader) and the send pump
+ // (outbound reader) both run on the reactor thread.
+ _inbound = new Pipe(new PipeOptions(
+ readerScheduler: scheduler,
+ writerScheduler: scheduler,
+ pauseWriterThreshold: 1024 * 1024,
+ resumeWriterThreshold: 512 * 1024,
+ useSynchronizationContext: false));
+
+ _outbound = new Pipe(new PipeOptions(
+ readerScheduler: scheduler,
+ writerScheduler: PipeScheduler.ThreadPool,
+ pauseWriterThreshold: 64 * 1024,
+ resumeWriterThreshold: 32 * 1024,
+ useSynchronizationContext: false));
+ }
+
+ /// Launches the recv and send pumps. Must be called on the reactor thread.
+ public void Start()
+ {
+ if (Interlocked.Exchange(ref _started, 1) == 1)
+ {
+ return;
+ }
+ _recvPump = RecvPumpAsync();
+ _sendPump = SendPumpAsync();
+ }
+
+ // Reactor → inbound pipe. Copies each recv slice into the pipe and flushes; Kestrel reads it.
+ private async Task RecvPumpAsync()
+ {
+ PipeWriter writer = _inbound.Writer;
+ try
+ {
+ while (true)
+ {
+ RecvSnapshot snap = await _conn.ReadAsync();
+
+ while (_conn.TryGetItem(snap, out SpscRecvRing.Item item))
+ {
+ if (item.HasBuffer && item.Len > 0)
+ {
+ CopySlice(in item, writer);
+ }
+ _conn.ReturnBuffer(in item);
+ }
+ _conn.ResetRead();
+
+ if (snap.IsClosed)
+ {
+ break;
+ }
+
+ FlushResult fr = await writer.FlushAsync();
+ if (fr.IsCompleted || fr.IsCanceled)
+ {
+ break;
+ }
+ }
+ }
+ catch { /* swallow client/protocol faults; teardown in finally */ }
+ finally { await writer.CompleteAsync(); }
+ }
+
+ private static unsafe void CopySlice(in SpscRecvRing.Item item, PipeWriter writer)
+ {
+ Span dst = writer.GetSpan(item.Len);
+ new ReadOnlySpan(item.Ptr, item.Len).CopyTo(dst);
+ writer.Advance(item.Len);
+ }
+
+ // Outbound pipe → connection send. Drains Kestrel's response into the slab and submits one SEND.
+ private async Task SendPumpAsync()
+ {
+ PipeReader reader = _outbound.Reader;
+ try
+ {
+ while (true)
+ {
+ ReadResult rr = await reader.ReadAsync();
+ ReadOnlySequence buffer = rr.Buffer;
+
+ if (!buffer.IsEmpty)
+ {
+ foreach (ReadOnlyMemory segment in buffer)
+ {
+ Span dst = _conn.GetSpan(segment.Length);
+ segment.Span.CopyTo(dst);
+ _conn.Advance(segment.Length);
+ }
+ await _conn.FlushAsync();
+ }
+
+ reader.AdvanceTo(buffer.End);
+
+ if (rr.IsCompleted || rr.IsCanceled)
+ {
+ break;
+ }
+ }
+ }
+ catch { /* swallow; teardown in finally */ }
+ finally { await reader.CompleteAsync(); }
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ // Kestrel has completed its ends; wake the pumps and unwind. MarkClosed wakes a recv parked in
+ // conn.ReadAsync — schedule it ON the reactor so the recv continuation (which touches reactor-owned
+ // recv state) runs there, not on Kestrel's dispose thread. The pipe cancels resume via the pipes'
+ // reactor reader/writer schedulers, so they're reactor-safe too.
+ _reactor.ScheduleOnReactor(static c => ((Connection)c!).MarkClosed(), _conn);
+ _inbound.Writer.CancelPendingFlush();
+ _outbound.Reader.CancelPendingRead();
+ try { await _recvPump; } catch { }
+ try { await _sendPump; } catch { }
+ }
+}
diff --git a/ioxide.Kestrel/IoxideConnectionContext.cs b/ioxide.Kestrel/IoxideConnectionContext.cs
new file mode 100644
index 0000000..9a77cd1
--- /dev/null
+++ b/ioxide.Kestrel/IoxideConnectionContext.cs
@@ -0,0 +1,93 @@
+using System.IO.Pipelines;
+using System.Net;
+using Microsoft.AspNetCore.Connections;
+using Microsoft.AspNetCore.Connections.Features;
+using Microsoft.AspNetCore.Http.Features;
+using ioxide;
+
+namespace ioxide.Kestrel;
+
+///
+/// Adapts a single ioxide to Kestrel's . The
+/// transport is a (BCL pipes whose reader schedulers route to the reactor),
+/// so Kestrel's read → parse → handle → send loop runs pinned to the reactor thread.
+///
+internal sealed class IoxideConnectionContext : ConnectionContext,
+ IConnectionIdFeature,
+ IConnectionTransportFeature,
+ IConnectionItemsFeature,
+ IConnectionLifetimeFeature,
+ IConnectionEndPointFeature
+{
+ private readonly HopDuplexPipe _pipe;
+ private readonly CancellationTokenSource _connectionClosedCts = new();
+ private readonly FeatureCollection _features = new();
+
+ // Completed when Kestrel is done with the connection (DisposeAsync, or Abort). The ioxide Handle
+ // callback awaits this and only then DecRefs the connection.
+ private readonly TaskCompletionSource _completion = new(TaskCreationOptions.RunContinuationsAsynchronously);
+
+ private int _disposed;
+
+ public IoxideConnectionContext(Connection connection, Reactor reactor, EndPoint localEndPoint, long id)
+ {
+ _pipe = new HopDuplexPipe(connection, reactor);
+
+ ConnectionId = $"ioxide-{id:x}";
+ LocalEndPoint = localEndPoint;
+ RemoteEndPoint = null;
+ Items = new ConnectionItems();
+ ConnectionClosed = _connectionClosedCts.Token;
+
+ _features.Set(this);
+ _features.Set(this);
+ _features.Set(this);
+ _features.Set(this);
+ _features.Set(this);
+ }
+
+ /// Resolves once Kestrel has finished with this connection; the reactor's Handle callback awaits it.
+ public Task Completion => _completion.Task;
+
+ /// Launches the transport pumps. Must be called on the reactor thread (from the Handle callback).
+ public void StartPumps() => _pipe.Start();
+
+ public override string ConnectionId { get; set; }
+ public override IFeatureCollection Features => _features;
+ public override IDictionary