diff --git a/tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj b/tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj
new file mode 100644
index 00000000..dc982453
--- /dev/null
+++ b/tests/GameCult.Caching.InteropPeer/GameCult.Caching.InteropPeer.csproj
@@ -0,0 +1,16 @@
+
+
+ Exe
+ net10.0
+ enable
+ GameCult.Caching.InteropPeer
+
+
+
+
+
+
+
+
diff --git a/tests/GameCult.Caching.InteropPeer/Program.cs b/tests/GameCult.Caching.InteropPeer/Program.cs
new file mode 100644
index 00000000..5de07335
--- /dev/null
+++ b/tests/GameCult.Caching.InteropPeer/Program.cs
@@ -0,0 +1,134 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using GameCult.Caching;
+using GameCult.Caching.MessagePack;
+using MessagePack;
+
+return await ProgramMainAsync(args);
+
+static async Task ProgramMainAsync(string[] args)
+{
+ try
+ {
+ if (args.Length == 0)
+ {
+ throw new InvalidOperationException("Expected mode: write | read");
+ }
+
+ var mode = args[0];
+ var options = ParseArgs(args.Skip(1).ToArray());
+ var file = RequireArg(options, "file");
+ switch (mode)
+ {
+ case "write":
+ await WriteAsync(file, RequireArg(options, "runtime-id"));
+ return 0;
+ case "read":
+ await ReadAsync(file);
+ return 0;
+ default:
+ throw new InvalidOperationException($"Unknown mode {mode}.");
+ }
+ }
+ catch (Exception error)
+ {
+ Console.Error.WriteLine(error);
+ return 1;
+ }
+}
+
+static async Task WriteAsync(string file, string runtimeId)
+{
+ var cache = BuildCache(file);
+ await cache.PullAllBackingStoresAsync();
+ var note = new CultCacheInteropNote
+ {
+ DocumentId = $"note:{runtimeId}",
+ AuthorRuntimeId = runtimeId,
+ Title = $"{runtimeId} wrote a CultCache note",
+ Body = "The v1 store format is the contract.",
+ Tags = [runtimeId, "csharp", "interop"]
+ };
+ await cache.AddAsync(note, new CultRecordHandle(new CultRecordKey(note.DocumentId)));
+ cache.FlushAllBackingStores();
+ WriteJsonLine(note);
+}
+
+static async Task ReadAsync(string file)
+{
+ var cache = BuildCache(file);
+ await cache.PullAllBackingStoresAsync();
+ var note = cache.AllEntries
+ .OfType()
+ .FirstOrDefault()
+ ?? throw new InvalidOperationException("No cultcache.interop-note records found.");
+ WriteJsonLine(note);
+}
+
+static CultCache BuildCache(string file)
+{
+ var cache = new CultCache();
+ cache.AddBackingStore(new SingleFileMessagePackBackingStore(file));
+ return cache;
+}
+
+static Dictionary ParseArgs(string[] args)
+{
+ var parsed = new Dictionary(StringComparer.OrdinalIgnoreCase);
+ for (var index = 0; index < args.Length; index += 2)
+ {
+ var token = args[index];
+ if (!token.StartsWith("--", StringComparison.Ordinal))
+ {
+ continue;
+ }
+
+ if (index + 1 >= args.Length)
+ {
+ throw new InvalidOperationException($"Missing value for {token}.");
+ }
+
+ parsed[token[2..]] = args[index + 1];
+ }
+
+ return parsed;
+}
+
+static string RequireArg(Dictionary options, string name)
+{
+ if (!options.TryGetValue(name, out var value) || string.IsNullOrWhiteSpace(value))
+ {
+ throw new InvalidOperationException($"Missing required argument --{name}.");
+ }
+
+ return value;
+}
+
+static void WriteJsonLine(CultCacheInteropNote note)
+{
+ Console.Out.WriteLine(JsonSerializer.Serialize(new
+ {
+ schemaVersion = note.SchemaVersion,
+ documentId = note.DocumentId,
+ authorRuntimeId = note.AuthorRuntimeId,
+ title = note.Title,
+ body = note.Body,
+ tags = note.Tags
+ }));
+}
+
+[CultDocument("cultcache.interop-note", "cultcache.interop_note.v1")]
+[MessagePackObject]
+public sealed class CultCacheInteropNote
+{
+ [Key(0)] public string SchemaVersion { get; set; } = "cultcache.interop_note.v1";
+ [Key(1)] [CultName] public string DocumentId { get; set; } = string.Empty;
+ [Key(2)] public string AuthorRuntimeId { get; set; } = string.Empty;
+ [Key(3)] public string Title { get; set; } = string.Empty;
+ [Key(4)] public string Body { get; set; } = string.Empty;
+ [Key(5)] public string[] Tags { get; set; } = Array.Empty();
+}