diff --git a/samples/isolated-entities/Chirper/.gitignore b/samples/isolated-entities/Chirper/.gitignore
new file mode 100644
index 000000000..1422b39dc
--- /dev/null
+++ b/samples/isolated-entities/Chirper/.gitignore
@@ -0,0 +1,13 @@
+# Build output
+bin/
+obj/
+
+# User-specific files
+*.user
+*.suo
+
+# Azure Functions local settings
+local.settings.json
+
+# Visual Studio cache
+.vs/
\ No newline at end of file
diff --git a/samples/isolated-entities/Chirper/Chirper.csproj b/samples/isolated-entities/Chirper/Chirper.csproj
new file mode 100644
index 000000000..dca8b42fe
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Chirper.csproj
@@ -0,0 +1,26 @@
+
+
+
+ net8.0
+ v4
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/isolated-entities/Chirper/Entities/IUserChirps.cs b/samples/isolated-entities/Chirper/Entities/IUserChirps.cs
new file mode 100644
index 000000000..bcc4c7954
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Entities/IUserChirps.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Chirper
+{
+ public interface IUserChirps
+ {
+ void Add(Chirp chirp);
+
+ void Remove(DateTime timestamp);
+
+ Task> Get();
+ }
+}
diff --git a/samples/isolated-entities/Chirper/Entities/IUserFollows.cs b/samples/isolated-entities/Chirper/Entities/IUserFollows.cs
new file mode 100644
index 000000000..6e7222285
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Entities/IUserFollows.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Chirper
+{
+ public interface IUserFollows
+ {
+ void Add(string user);
+
+ void Remove(string user);
+
+ Task> Get();
+ }
+}
diff --git a/samples/isolated-entities/Chirper/Entities/UserChirps.cs b/samples/isolated-entities/Chirper/Entities/UserChirps.cs
new file mode 100644
index 000000000..1ceef9284
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Entities/UserChirps.cs
@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Microsoft.Azure.Functions.Worker;
+using Newtonsoft.Json;
+
+namespace Chirper
+{
+ // The UserChirps entity stores all the chirps by ONE user.
+ // The entity key is the userId.
+
+ [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
+ public class UserChirps : IUserChirps
+ {
+ [JsonProperty]
+ public List Chirps { get; set; } = new List();
+
+ public void Add(Chirp chirp)
+ {
+ Chirps.Add(chirp);
+ }
+
+ public void Remove(DateTime timestamp)
+ {
+ Chirps.RemoveAll(chirp => chirp.Timestamp == timestamp);
+ }
+
+ public Task> Get()
+ {
+ return Task.FromResult(Chirps);
+ }
+
+ // Boilerplate (entry point for the functions runtime)
+ [Function(nameof(UserChirps))]
+ public static Task HandleEntityOperation([EntityTrigger] TaskEntityDispatcher context)
+ {
+ return context.DispatchAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/isolated-entities/Chirper/Entities/UserFollows.cs b/samples/isolated-entities/Chirper/Entities/UserFollows.cs
new file mode 100644
index 000000000..93e4efd27
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Entities/UserFollows.cs
@@ -0,0 +1,40 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Microsoft.Azure.Functions.Worker;
+using Newtonsoft.Json;
+
+namespace Chirper
+{
+ // The UserFollows entity stores all the follows of ONE user.
+ // The entity key is the userId.
+
+ [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
+ public class UserFollows : IUserFollows
+ {
+ [JsonProperty]
+ public List FollowedUsers { get; set; } = new List();
+
+ public void Add(string user)
+ {
+ FollowedUsers.Add(user);
+ }
+
+ public void Remove(string user)
+ {
+ FollowedUsers.Remove(user);
+ }
+
+ public Task> Get()
+ {
+ return Task.FromResult(FollowedUsers);
+ }
+
+ // Boilerplate (entry point for the functions runtime)
+ [Function(nameof(UserFollows))]
+ public static Task HandleEntityOperation([EntityTrigger] TaskEntityDispatcher context)
+ {
+ return context.DispatchAsync();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/isolated-entities/Chirper/Orchestrations/GetTimeline.cs b/samples/isolated-entities/Chirper/Orchestrations/GetTimeline.cs
new file mode 100644
index 000000000..a7392fd8b
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Orchestrations/GetTimeline.cs
@@ -0,0 +1,44 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Entities;
+
+namespace Chirper
+{
+ // The GetTimeline orchestration collects all chirps by followed users,
+ // and returns it as a list sorted by timestamp.
+ public static class GetTimeline
+ {
+ [Function(nameof(GetTimeline))]
+ public static async Task RunOrchestrator(
+ [OrchestrationTrigger] TaskOrchestrationContext context)
+ {
+ var userId = context.GetInput();
+
+ // call the UserFollows entity to figure out whose chirps should be included
+ var userFollowsId = new EntityInstanceId(nameof(UserFollows), userId);
+ var followedUsers = await context.Entities.CallEntityAsync>(userFollowsId, "Get");
+
+
+ // in parallel, collect all the chirps
+ var tasks = followedUsers
+ .Select(id =>
+ {
+ var userChirpsId = new EntityInstanceId(nameof(UserChirps), id);
+ return context.Entities.CallEntityAsync>(userChirpsId, "Get");
+ })
+ .ToList();
+
+ await Task.WhenAll(tasks);
+
+ // combine and sort the returned lists of chirps
+ var sortedResults = tasks
+ .SelectMany(task => task.Result)
+ .OrderBy(chirp => chirp.Timestamp);
+
+ return sortedResults.ToArray();
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/isolated-entities/Chirper/Program.cs b/samples/isolated-entities/Chirper/Program.cs
new file mode 100644
index 000000000..704475c64
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Program.cs
@@ -0,0 +1,14 @@
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+var builder = FunctionsApplication.CreateBuilder(args);
+
+builder.ConfigureFunctionsWebApplication();
+
+builder.Services
+ .AddApplicationInsightsTelemetryWorkerService()
+ .ConfigureFunctionsApplicationInsights();
+
+builder.Build().Run();
diff --git a/samples/isolated-entities/Chirper/Properties/serviceDependencies.json b/samples/isolated-entities/Chirper/Properties/serviceDependencies.json
new file mode 100644
index 000000000..df4dcc9d8
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Properties/serviceDependencies.json
@@ -0,0 +1,11 @@
+{
+ "dependencies": {
+ "appInsights1": {
+ "type": "appInsights"
+ },
+ "storage1": {
+ "type": "storage",
+ "connectionId": "AzureWebJobsStorage"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/isolated-entities/Chirper/Properties/serviceDependencies.local.json b/samples/isolated-entities/Chirper/Properties/serviceDependencies.local.json
new file mode 100644
index 000000000..b804a2893
--- /dev/null
+++ b/samples/isolated-entities/Chirper/Properties/serviceDependencies.local.json
@@ -0,0 +1,11 @@
+{
+ "dependencies": {
+ "appInsights1": {
+ "type": "appInsights.sdk"
+ },
+ "storage1": {
+ "type": "storage.emulator",
+ "connectionId": "AzureWebJobsStorage"
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/isolated-entities/Chirper/PublicRest/Chirp.cs b/samples/isolated-entities/Chirper/PublicRest/Chirp.cs
new file mode 100644
index 000000000..92c4693e1
--- /dev/null
+++ b/samples/isolated-entities/Chirper/PublicRest/Chirp.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Serialization;
+
+namespace Chirper
+{
+ ///
+ /// A data structure representing a chirp.
+ ///
+ [JsonObject(MemberSerialization.OptOut)]
+ public struct Chirp
+ {
+ public string UserId { get; set; }
+
+ public DateTime Timestamp { get; set; }
+
+ public string Content { get; set; }
+ }
+}
diff --git a/samples/isolated-entities/Chirper/PublicRest/HttpSurface.cs b/samples/isolated-entities/Chirper/PublicRest/HttpSurface.cs
new file mode 100644
index 000000000..720d7e7c6
--- /dev/null
+++ b/samples/isolated-entities/Chirper/PublicRest/HttpSurface.cs
@@ -0,0 +1,145 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+using System.Net;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Client.Entities;
+using Microsoft.DurableTask.Entities;
+using Microsoft.Extensions.Logging;
+
+namespace Chirper
+{
+ public static class HttpSurface
+ {
+ [Function("UserTimelineGet")]
+ public static async Task UserTimelineGet(
+ [HttpTrigger(AuthorizationLevel.Function, "get", Route = "user/{userId}/timeline")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId)
+ {
+ Authenticate(req, userId);
+ var instanceId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(GetTimeline), userId);
+ return await client.CreateCheckStatusResponseAsync(req, instanceId);
+ }
+
+ [Function("UserChirpsGet")]
+ public static async Task UserChirpsGet(
+ [HttpTrigger(AuthorizationLevel.Function, "get", Route = "user/{userId}/chirps")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId)
+ {
+ Authenticate(req, userId);
+ var target = new EntityInstanceId(nameof(UserChirps), userId);
+ var chirps = await client.Entities.GetEntityAsync(target);
+
+ if (chirps != null && chirps.State != null)
+ {
+ var response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(chirps.State.Chirps);
+ return response;
+ }
+
+ var notFoundResponse = req.CreateResponse(HttpStatusCode.NotFound);
+ return notFoundResponse;
+ }
+
+ [Function("UserChirpsPost")]
+ public static async Task UserChirpsPost(
+ [HttpTrigger(AuthorizationLevel.Function, "post", Route = "user/{userId}/chirps")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId)
+ {
+ Authenticate(req, userId);
+ var chirp = new Chirp()
+ {
+ UserId = userId,
+ Timestamp = DateTime.UtcNow,
+ Content = await req.ReadAsStringAsync() ?? string.Empty,
+ };
+
+ var entityInstanceId = new EntityInstanceId(nameof(UserChirps), userId);
+ await client.Entities.SignalEntityAsync(entityInstanceId, "Add", chirp);
+
+ var response = req.CreateResponse(HttpStatusCode.Accepted);
+ await response.WriteAsJsonAsync(chirp);
+
+ return response;
+ }
+
+ [Function("UserChirpsDelete")]
+ public static async Task UserChirpsDelete(
+ [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "user/{userId}/chirps/{timestamp}")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId,
+ DateTime timestamp)
+ {
+ Authenticate(req, userId);
+
+ var entityInstanceId = new EntityInstanceId(nameof(UserChirps), userId);
+ await client.Entities.SignalEntityAsync(entityInstanceId, "Remove", timestamp);
+
+ return req.CreateResponse(HttpStatusCode.Accepted);
+ }
+
+ [Function("UserFollowsGet")]
+ public static async Task UserFollowsGet(
+ [HttpTrigger(AuthorizationLevel.Function, "get", Route = "user/{userId}/follows")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId)
+ {
+ Authenticate(req, userId);
+ var target = new EntityInstanceId(nameof(UserFollows), userId);
+ EntityMetadata? follows = await client.Entities.GetEntityAsync(target);
+
+ if (follows != null)
+ {
+ HttpResponseData response = req.CreateResponse(HttpStatusCode.OK);
+ await response.WriteAsJsonAsync(follows.State.FollowedUsers);
+ return response;
+ }
+
+ return req.CreateResponse(HttpStatusCode.NotFound);
+ }
+
+ [Function("UserFollowsPost")]
+ public static async Task UserFollowsPost(
+ [HttpTrigger(AuthorizationLevel.Function, "post", Route = "user/{userId}/follows/{userId2}")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId,
+ string userId2)
+ {
+ Authenticate(req, userId);
+ var entityInstanceId = new EntityInstanceId(nameof(UserFollows), userId);
+ await client.Entities.SignalEntityAsync(entityInstanceId, "Add", userId2);
+ return req.CreateResponse(HttpStatusCode.Accepted);
+ }
+
+ [Function("UserFollowsDelete")]
+ public static async Task UserFollowsDelete(
+ [HttpTrigger(AuthorizationLevel.Function, "delete", Route = "user/{userId}/follows/{userId2}")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client,
+ ILogger log,
+ string userId,
+ string userId2)
+ {
+ Authenticate(req, userId);
+ var content = req.Body.ToString();
+ var entityInstanceId = new EntityInstanceId(nameof(UserFollows), userId);
+ await client.Entities.SignalEntityAsync(entityInstanceId, "Remove", userId2);
+ return req.CreateResponse(HttpStatusCode.Accepted);
+ }
+
+ private static void Authenticate(HttpRequestData request, string userId)
+ {
+ // Stub: validate that the request is coming from this userId
+ }
+ }
+}
diff --git a/samples/isolated-entities/Chirper/README.md b/samples/isolated-entities/Chirper/README.md
new file mode 100644
index 000000000..9a0312be0
--- /dev/null
+++ b/samples/isolated-entities/Chirper/README.md
@@ -0,0 +1,104 @@
+# Chirper Sample
+
+This sample demonstrates how to create a simple, stateful, serverless REST service using
+Azure Functions and Durable Functions .NET Isolated.
+
+The users of this service can:
+
+1. post messages (called "chirps") to their account, or delete them
+2. follow or unfollow other users
+3. view a timeline containing all chirps of the people they are following, sorted by timestamp
+
+This sample is meant to highlight support for stateful entities and how they
+can be used in conjunction with Durable Orchestrations. It is based on
+[sample code from a paper](https://www.microsoft.com/en-us/research/publication/reactive-caching-for-composed-services/),
+which was itself inspired by the Chirper virtual actor sample included with the
+[Orleans framework](https://github.com/dotnet/orleans).
+
+
+## Design
+
+The function application contains a total of 10 Azure Functions.
+
+Two of the functions are *durable entities*, implementing the stateful components.
+
+ - The **UserChirps** entity stores a list of chirps, representing the chirps by a particular user.
+ There is one UserChirps entity per user: The entity key is the userId. The operations are *Add* (adds a chirp),
+ *Remove* (removes a chirp) and *Get* (returns the list of chirps).
+
+ - The **UserFollows** entity stores the set of users that a particular user is following.
+ There is one UserFollows entity per user: The entity key is the userId. The operations are *Add* (follows a user),
+ *Remove* (unfollows a user) and *Get* (returns the list of followed users).
+
+One of the functions is a *durable orchestration*, implementing the timeline query.
+
+ - The **GetTimeline** orchestration collects the chirps for the timeline of a particular user.
+ It first calls the `UserFollows` entity to get a list of the followed users. Then it calls the UserChirps
+ entities of all the followed users, *in parallel*. Once it receives all the lists
+ it combines them and sorts them.
+
+Seven of the functions are Http triggers that implement the REST interface (see next section for a list). Each of them specifies a path, and uses
+the IDurableOrchestrationClient to access the durable entities and durable orchestration.
+
+ - The POST methods signal the respective entities, and returns 202.
+ - The GET methods for chirps or follows read the entity state .
+ - The GET method for the timeline calls the GetTimelineOrchestration and returns either the result,
+ or a 202 including an URL for status.
+
+
+## Running The Sample Locally with VS
+
+Open Chirper.sln in Visual Studio, compile, and run.
+
+On Windows, this automatically starts the local development storage.
+On macOS, you can edit the `local.settings.json` and replace `UseDevelopmentStorage=true` with a connection string to an Azure storage account.
+
+Once the function runtime starts successfully, the console shows the progress.
+After some time it prints a list of the HTTP bindings:
+
+ UserChirpsDelete: [DELETE] http://localhost:7071/api/user/{userId}/chirps/{timestamp}
+ UserChirpsGet: [GET] http://localhost:7071/api/user/{userId}/chirps
+ UserChirpsPost: [POST] http://localhost:7071/api/user/{userId}/chirps
+ UserFollowsDelete: [DELETE] http://localhost:7071/api/user/{userId}/follows/{userId2}
+ UserFollowsGet: [GET] http://localhost:7071/api/user/{userId}/follows
+ UserFollowsPost: [POST] http://localhost:7071/api/user/{userId}/follows/{userId2}
+ UserTimelineGet: [GET] http://localhost:7071/api/user/{userId}/timeline
+
+
+You can now use [curl](https://github.com/curl/curl) (or any other tool that lets you compose HTTP requests)
+to test the chirper service via these endpoints.
+
+### Sample interaction
+
+For example, let's say Alice adds three chirps using POST:
+
+ curl -d "Alice's first message" http://localhost:7071/api/user/alice/chirps -H Content-Type:application/text
+ curl -d "Alice's second message" http://localhost:7071/api/user/alice/chirps -H Content-Type:application/text
+ curl -d "Alice's third message" http://localhost:7071/api/user/alice/chirps -H Content-Type:application/text
+
+We can then query Alice's chirps using GET:
+
+ curl http://localhost:7071/user/alice/chirps
+
+which returns a JSON representation of all the chirps by Alice, including timestamps:
+
+ [{"userId":"alice","timestamp":"2019-05-01T15:45:42.2223472Z","content":"Alice's first message"},{"userId":"alice","timestamp":"2019-05-01T15:45:44.7693918Z","content":"Alice's second message"},{"userId":"alice","timestamp":"2019-05-01T15:45:45.7658774Z","content":"Alice's third message"}]
+
+Let's add some more messages by other users:
+
+ curl -d "Bob's first message" http://localhost:7071/api/user/bob/chirps -H Content-Type:application/text
+ curl -d "Charlie's first message" http://localhost:7071/api/user/charlie/chirps -H Content-Type:application/text
+ curl -d "Bob's second message" http://localhost:7071/api/user/bob/chirps -H Content-Type:application/text
+
+Then, let's say Doris wants to follow Alice, Bob, and Charlie
+
+ curl -d "" http://localhost:7071/api/user/doris/follows/alice
+ curl -d "" http://localhost:7071/api/user/doris/follows/bob
+ curl -d "" http://localhost:7071/api/user/doris/follows/charlie
+
+Finally, if Doris queries the timeline now, she will see all the messages of Alice, Bob, and Charlie, sorted by timestamp.
+
+ curl http://localhost:7071/api/user/doris/timeline
+
+
+
diff --git a/samples/isolated-entities/Chirper/host.json b/samples/isolated-entities/Chirper/host.json
new file mode 100644
index 000000000..ee5cf5f83
--- /dev/null
+++ b/samples/isolated-entities/Chirper/host.json
@@ -0,0 +1,12 @@
+{
+ "version": "2.0",
+ "logging": {
+ "applicationInsights": {
+ "samplingSettings": {
+ "isEnabled": true,
+ "excludedTypes": "Request"
+ },
+ "enableLiveMetricsFilters": true
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/isolated-entities/InventorySample/.gitignore b/samples/isolated-entities/InventorySample/.gitignore
new file mode 100644
index 000000000..1422b39dc
--- /dev/null
+++ b/samples/isolated-entities/InventorySample/.gitignore
@@ -0,0 +1,13 @@
+# Build output
+bin/
+obj/
+
+# User-specific files
+*.user
+*.suo
+
+# Azure Functions local settings
+local.settings.json
+
+# Visual Studio cache
+.vs/
\ No newline at end of file
diff --git a/samples/isolated-entities/InventorySample/ChargePayment.cs b/samples/isolated-entities/InventorySample/ChargePayment.cs
new file mode 100644
index 000000000..b3dc1b7ea
--- /dev/null
+++ b/samples/isolated-entities/InventorySample/ChargePayment.cs
@@ -0,0 +1,14 @@
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.DurableTask;
+using InventorySample;
+
+public static class ChargePayment
+{
+ [Function(nameof(ChargePayment))]
+ public static Task Run([ActivityTrigger] OrderRequest order)
+ {
+ // Simulate payment processing — always succeed for now
+ Console.WriteLine($"Charging {order.Amount:C} for Order {order.OrderId}");
+ return Task.FromResult(true);
+ }
+}
diff --git a/samples/isolated-entities/InventorySample/InventoryEndpoints.cs b/samples/isolated-entities/InventorySample/InventoryEndpoints.cs
new file mode 100644
index 000000000..1ad862716
--- /dev/null
+++ b/samples/isolated-entities/InventorySample/InventoryEndpoints.cs
@@ -0,0 +1,77 @@
+using System.Net;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.Azure.Functions.Worker.Http;
+using Microsoft.DurableTask.Client;
+using Microsoft.DurableTask.Entities;
+
+public static class InventoryEndpoints
+{
+ // POST /orders
+ [Function("StartOrder")]
+ public static async Task StartOrder(
+ [HttpTrigger(AuthorizationLevel.Function, "post", Route = "orders")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client)
+ {
+ var order = await JsonSerializer.DeserializeAsync(req.Body, SerializerOptions());
+ if (order is null)
+ {
+ var badRes = req.CreateResponse(HttpStatusCode.BadRequest);
+ await badRes.WriteStringAsync("Invalid order payload.");
+ return badRes;
+ }
+
+ await client.ScheduleNewOrchestrationInstanceAsync("OrderOrchestrator", order);
+
+ var res = req.CreateResponse(HttpStatusCode.Accepted);
+ await res.WriteStringAsync($"Order {order.OrderId} started.");
+ return res;
+ }
+
+ // POST /inventory/setup
+ [Function("SetupInventory")]
+ public static async Task SetupInventory(
+ [HttpTrigger(AuthorizationLevel.Function, "post", Route = "inventory/setup")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client)
+ {
+ var inventory = await JsonSerializer.DeserializeAsync>(req.Body, SerializerOptions());
+ if (inventory is null)
+ {
+ var badRes = req.CreateResponse(HttpStatusCode.BadRequest);
+ await badRes.WriteStringAsync("Invalid inventory payload.");
+ return badRes;
+ }
+
+ foreach (var (sku, qty) in inventory)
+ {
+ await client.Entities.SignalEntityAsync(
+ new EntityInstanceId("InventoryEntity", "store"),
+ "AddOrUpdate",
+ new Item(sku, qty));
+ }
+
+ var res = req.CreateResponse(HttpStatusCode.OK);
+ await res.WriteStringAsync("Inventory updated.");
+ return res;
+ }
+
+ // GET /inventory
+ [Function("GetInventory")]
+ public static async Task GetInventory(
+ [HttpTrigger(AuthorizationLevel.Function, "get", Route = "inventory")] HttpRequestData req,
+ [DurableClient] DurableTaskClient client)
+ {
+ string instanceId = $"query-inventory-{Guid.NewGuid()}";
+
+ await client.ScheduleNewOrchestrationInstanceAsync("QueryInventory", instanceId);
+
+ return await client.CreateCheckStatusResponseAsync(req, instanceId);
+ }
+
+ private static JsonSerializerOptions SerializerOptions() => new()
+ {
+ PropertyNameCaseInsensitive = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
+ };
+}
diff --git a/samples/isolated-entities/InventorySample/InventoryEntity.cs b/samples/isolated-entities/InventorySample/InventoryEntity.cs
new file mode 100644
index 000000000..932a2e044
--- /dev/null
+++ b/samples/isolated-entities/InventorySample/InventoryEntity.cs
@@ -0,0 +1,50 @@
+using Microsoft.DurableTask.Entities;
+using Microsoft.Azure.Functions.Worker;
+using InventorySample;
+
+public class InventoryEntity
+{
+ public Dictionary Stock { get; set; } = new();
+
+ [Function(nameof(InventoryEntity))]
+ public static Task HandleAsync([EntityTrigger] TaskEntityDispatcher dispatcher)
+ => dispatcher.DispatchAsync();
+
+ public List- GetAll()
+ {
+ return Stock.Select(item => new Item(item.Key, item.Value)).ToList();
+ }
+
+ public void AddOrUpdate(Item update)
+ {
+ if (!Stock.ContainsKey(update.Sku))
+ Stock[update.Sku] = 0;
+
+ Stock[update.Sku] += update.Qty;
+ }
+
+ public bool TryRemoveMany(List items)
+ {
+ foreach (var item in items)
+ if (!Stock.ContainsKey(item.Sku) || Stock[item.Sku] < item.Qty)
+ return false;
+
+ foreach (var item in items)
+ Stock[item.Sku] -= item.Qty;
+
+ return true;
+ }
+
+ public void AddMany(List items)
+ {
+ foreach (var item in items)
+ {
+ if (!Stock.ContainsKey(item.Sku))
+ Stock[item.Sku] = 0;
+
+ Stock[item.Sku] += item.Qty;
+ }
+ }
+}
+
+public record Item(string Sku, int Qty);
diff --git a/samples/isolated-entities/InventorySample/InventorySample.csproj b/samples/isolated-entities/InventorySample/InventorySample.csproj
new file mode 100644
index 000000000..d00c2c0c4
--- /dev/null
+++ b/samples/isolated-entities/InventorySample/InventorySample.csproj
@@ -0,0 +1,22 @@
+
+
+
+ net8.0
+ v4
+ Exe
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/isolated-entities/InventorySample/OrderOrchestrator.cs b/samples/isolated-entities/InventorySample/OrderOrchestrator.cs
new file mode 100644
index 000000000..4a3bbfc37
--- /dev/null
+++ b/samples/isolated-entities/InventorySample/OrderOrchestrator.cs
@@ -0,0 +1,40 @@
+using InventorySample;
+using Microsoft.Azure.Functions.Worker;
+using Microsoft.DurableTask;
+using Microsoft.DurableTask.Entities;
+
+public static class OrderOrchestrator
+{
+ [Function(nameof(OrderOrchestrator))]
+ public static async Task Run(
+ [OrchestrationTrigger] TaskOrchestrationContext context)
+ {
+ var input = context.GetInput()!;
+ var entityId = new EntityInstanceId(nameof(InventoryEntity), "store");
+
+ var stockOk = await context.Entities.CallEntityAsync(
+ entityId, "TryRemoveMany", input.Items);
+
+ if (!stockOk)
+ return $"Order {input.OrderId} failed: not enough stock.";
+
+ var paid = await context.CallActivityAsync("ChargePayment", input);
+ if (!paid)
+ {
+ await context.Entities.CallEntityAsync