diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs
new file mode 100644
index 00000000..0b025af8
--- /dev/null
+++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Scripts/SiteScriptCompileCache.cs
@@ -0,0 +1,84 @@
+using System.Collections.Concurrent;
+using System.Security.Cryptography;
+using System.Text;
+
+namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
+
+///
+/// Process-wide cache of site-side script s, keyed on
+/// the globals type plus a SHA-256 hash of the script code. Unlike the TemplateEngine's
+/// ScriptCompileVerdictCache (which caches central-validation verdicts), this
+/// caches the full compiled result — the immutable Roslyn Script<object?> itself —
+/// so the synchronous deploy gate and the InstanceActor.PreStart recompile share one
+/// Roslyn compile per script body per process lifetime (N4).
+///
+/// Soundness rests on three invariants:
+///
+/// A compile result is a pure function of (code, globals type, compile-time-static
+/// trust policy) — none of which vary at runtime for a given key.
+/// The stored error text is name-free (Roslyn Diagnostic.GetMessage() /
+/// "Compilation exception: …"); callers prefix their own script-name context, so a
+/// shared cached failure renders correctly for every caller. Failures are cached too —
+/// a bad script body fails identically for every caller.
+/// Roslyn Script<T> is immutable and thread-safe (RunAsync creates
+/// per-run state), so one compiled instance may be shared across actors and threads.
+///
+///
+///
+/// Bounded at entries — deliberately smaller than the verdict cache's
+/// 4096 because entries pin compiled assemblies, not just verdict strings. On overflow the cache
+/// is cleared wholesale (results are recomputable, so a coarse reset avoids eviction bookkeeping).
+/// // are exposed for tests and diagnostics.
+///
+///
+internal static class SiteScriptCompileCache
+{
+ /// Upper bound on cached entries; the cache is cleared wholesale on overflow.
+ internal const int MaxEntries = 1024;
+
+ private static readonly ConcurrentDictionary Cache = new();
+ private static long _hits;
+
+ /// Number of cache hits observed since the last .
+ public static long Hits => Interlocked.Read(ref _hits);
+
+ /// Current number of cached compile results.
+ public static int Count => Cache.Count;
+
+ ///
+ /// Returns the cached compile result for against
+ /// , or computes it via and caches
+ /// it on a miss. A hit increments . Both success and failure results are
+ /// cached — the error text is name-free by construction.
+ ///
+ /// The script source code to look up (hashed to form the cache key).
+ /// The Roslyn globals surface the script compiles against; part of the key so identical source under different globals stays distinct.
+ /// Computes the compile result on a cache miss.
+ /// The cached or newly computed compile result.
+ public static ScriptCompilationResult GetOrAdd(string code, Type globalsType, Func factory)
+ {
+ var key = globalsType.FullName + ":" + Convert.ToHexString(SHA256.HashData(Encoding.UTF8.GetBytes(code)));
+
+ if (Cache.TryGetValue(key, out var result))
+ {
+ Interlocked.Increment(ref _hits);
+ return result;
+ }
+
+ result = factory();
+
+ // Coarse bound: on overflow drop everything rather than track evictions.
+ if (Cache.Count >= MaxEntries)
+ Cache.Clear();
+
+ Cache[key] = result;
+ return result;
+ }
+
+ /// Clears all cached compile results and resets the hit counter.
+ public static void Clear()
+ {
+ Cache.Clear();
+ Interlocked.Exchange(ref _hits, 0);
+ }
+}
diff --git a/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Scripts/SiteScriptCompileCacheTests.cs b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Scripts/SiteScriptCompileCacheTests.cs
new file mode 100644
index 00000000..435863a9
--- /dev/null
+++ b/tests/ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests/Scripts/SiteScriptCompileCacheTests.cs
@@ -0,0 +1,54 @@
+using Microsoft.CodeAnalysis.CSharp.Scripting;
+using Microsoft.CodeAnalysis.Scripting;
+using ZB.MOM.WW.ScadaBridge.SiteRuntime.Scripts;
+
+namespace ZB.MOM.WW.ScadaBridge.SiteRuntime.Tests.Scripts;
+
+///
+/// Tests for the process-wide . The cache is
+/// static/process-wide state, so this class shares the "SiteScriptCompileCache" xunit
+/// collection with ScriptCompilationServiceTests (no cross-class parallelism) and
+/// calls at the top of each test.
+///
+[Collection("SiteScriptCompileCache")]
+public class SiteScriptCompileCacheTests
+{
+ private static ScriptCompilationResult Ok() =>
+ ScriptCompilationResult.Succeeded(CSharpScript.Create