# Sandbox Engine — PRD

## Status

Implemented

## Executive Summary

The Sandbox Engine gives the ows-coda AI agent the ability to **execute code** — running computations, generating charts, building financial models, and optimizing royalty portfolios — all inside secure, ephemeral V8 isolates. By embedding a full analytical runtime (DuckDB for SQL, GLPK for optimization, simple-statistics for math) directly into the agent's tool surface, this feature transforms ows-coda from a data lookup assistant into an autonomous analytical partner that can model, compute, visualize, and optimize on behalf of royalty analysts.

## The Problem

A royalty analyst opens ows-coda and asks: _"What would our revenue look like if we renegotiated this contract at 15% instead of 12%?"_

The agent looks up the current contract terms. It pulls the revenue history. It returns the numbers in a nicely formatted table. And then it stops. It describes what the analyst would need to calculate, but it cannot actually do the math. It cannot run the what-if scenario. It cannot project the revenue delta over the next three years. It cannot generate a chart comparing the two rate structures side by side.

The analyst copies the numbers into Excel. They build a spreadsheet. They format a chart. They email it to their manager. The entire workflow that the AI was supposed to accelerate has been offloaded back to a manual process — the agent served as a glorified database browser.

This pattern repeats across every analytical question the platform could answer:

- **"Which of our top 50 accounts are trending below their advance recoupment schedule?"** The agent can pull the data for each account, but it cannot compute recoupment rates, flag outliers, or rank them. The analyst exports to Excel and does the work themselves.
- **"Show me a breakdown of revenue by territory for this catalog, quarter over quarter."** The agent returns raw rows. It cannot pivot, aggregate, or visualize. The analyst pastes into Google Sheets and builds the pivot table manually.
- **"If we allocated $2M in advances across these 15 artists, what's the optimal distribution to maximize expected return?"** The agent cannot even attempt this. Portfolio optimization requires linear programming. The analyst either does it by gut instinct or escalates to a data science team with a two-week turnaround.
- **"Compare the royalty yield of these three catalogs over the last 8 quarters and tell me which one is the best acquisition target."** The agent can fetch the data, but comparative analysis with trend lines, growth rates, and weighted scoring is computation — not retrieval.

The fundamental limitation is architectural. The agent's tool surface is read-only: it can query databases, look up records, and format responses. But it has no execution environment. It cannot run a single line of arithmetic, let alone a statistical model or an optimization solver. Every question that requires _transformation_ of data — not just _retrieval_ of data — hits a wall.

Users know this. They have learned that ows-coda is a fast way to look things up, but not a place to think. The AI describes what an analyst should do; it does not do the analysis. The gap between "here are the numbers" and "here is the answer" is the gap this project closes.

## The Opportunity

Code execution changes the AI from a lookup tool into a full analytical partner. When the agent can run code, every question that currently ends with "here are the numbers, now go to Excel" instead ends with the answer.

The what-if scenario? The agent writes JavaScript, queries the current contract terms via its data proxy, computes the revenue projection at both rate structures, and returns a formatted comparison. The recoupment analysis? It pulls all 50 accounts, calculates recoupment velocity for each, flags the ones trending below schedule, and ranks them by severity. The portfolio optimization? It formulates the problem as a linear program, feeds it to GLPK, and returns the optimal allocation with sensitivity analysis.

Three WASM modules make this possible without leaving the V8 isolate:

- **DuckDB-WASM** — columnar SQL engine. The agent can pull raw data from Snowflake via the data proxy and then run complex aggregations, pivots, window functions, and joins entirely in-memory. This replaces the pattern of making dozens of separate database queries to assemble an answer.
- **GLPK.js** — mixed-integer linear programming solver. Advance allocation, catalog portfolio optimization, rate structure comparison under constraints — problems that currently require a data science team or manual guesswork become something the agent solves in seconds.
- **simple-statistics** — regression, distributions, descriptive statistics. Trend analysis, growth rate computation, outlier detection, confidence intervals. The statistical backbone for every "is this trending up or down?" question.

The platform becomes extensible. New WASM modules can be added without changing the sandbox architecture. A charting library for inline visualizations. A time-series forecasting module. Each one expands what the agent can do without expanding the security surface — everything runs inside the same memory-gapped V8 isolate.

The competitive moat deepens. Any AI agent can query a database. Very few can run arbitrary computation safely, with resource limits, audit logging, and WASM-powered analytical libraries. This is the difference between a chatbot and a platform.

## Goals & Success Criteria

| Goal                         | Success Criteria                                                                                                                                                                                                              |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Secure code execution        | Zero isolate escapes or host process compromises. All LLM-generated code runs inside memory-gapped V8 isolates with no access to Node.js APIs, filesystem, or network.                                                        |
| Bounded resource consumption | Every execution terminates within wall-clock and memory limits. No single execution can exhaust host resources. 5-tier limit enforcement (isolate, bridge, conversation turn, per-user, system) all operational.              |
| Sub-second cold starts       | Warm pool isolate acquisition completes in <5ms. Cold-start isolate creation completes in <50ms.                                                                                                                              |
| Analytical capability        | Agent can perform computations (arithmetic, statistical), run SQL transformations (DuckDB), and solve optimization problems (GLPK) within a single conversation turn.                                                         |
| Transparent execution        | Users see results (computed answers, formatted tables, charts), not code. Execution is invisible unless the user asks to see it. Status events stream progress for long-running computations.                                 |
| Extensible platform          | New WASM modules can be added via configuration without code changes to the sandbox engine.                                                                                                                                   |
| Production readiness         | Integration tests cover: timeout enforcement (sync and async loops), OOM handling, code injection attempts, request amplification, result flooding, console spam, Function constructor escape, and oversized code submission. |
| Observability                | Every data request from the sandbox is audit-logged with requestId, method, params, user identity, and timestamp. Execution stats (wallMs, cpuMs, heapMb, dataRequestCount) returned with every result.                       |

## User Stories

**1. Computation: What-if revenue modeling**
As a royalty analyst, I want to ask "What would revenue look like if we changed this contract rate from 12% to 15%?" and receive a computed projection with dollar amounts, so that I can evaluate contract renegotiation without building a spreadsheet.

**2. Visualization: Revenue breakdown by territory**
As a catalog manager, I want to ask "Show me quarterly revenue by territory for this catalog" and receive a chart or formatted pivot table, so that I can present territory performance to stakeholders without exporting data to Excel.

**3. What-if modeling: Advance recoupment scenarios**
As a finance analyst, I want to ask "Which of our top 50 accounts are trending below their advance recoupment schedule?" and receive a ranked list with recoupment velocity calculations and flags for at-risk accounts, so that I can prioritize collection efforts.

**4. Data transformation: Cross-catalog comparison**
As an A&R executive, I want to ask "Compare the royalty yield of these three catalogs over the last 8 quarters" and receive a comparative analysis with growth rates, trend lines, and a recommendation, so that I can make acquisition decisions backed by data.

**5. Optimization: Advance portfolio allocation**
As a VP of Finance, I want to ask "If we have $2M to allocate across these 15 artists as advances, what's the optimal distribution to maximize expected return?" and receive a GLPK-optimized allocation plan with sensitivity analysis, so that I can deploy capital efficiently instead of relying on gut instinct.

**6. Statistical analysis: Outlier and trend detection**
As an operations analyst, I want to ask "Are there any anomalies in this quarter's royalty distributions compared to the trailing 4-quarter average?" and receive a statistical analysis that flags outliers with z-scores and confidence intervals, so that I can catch errors before payments go out.

**7. Multi-step analysis: Compound questions**
As a senior analyst, I want to ask a compound question like "Pull revenue for these 10 accounts, calculate year-over-year growth for each, rank them, and tell me which ones are accelerating vs. decelerating" and have the agent execute the entire pipeline in one turn — pulling data, computing growth rates, classifying trends, and ranking — so that I get a complete answer without asking five separate questions.

**8. Transparent failure: Graceful resource limits**
As any user, when my question requires computation that exceeds resource limits (too much data, too long a computation), I want the agent to explain what it tried, why it hit a limit, and suggest a narrower question, so that I understand the constraint and can adjust rather than seeing a cryptic error.

## Proposed Solution

### Architecture: V8 Isolates with Message-Passing

The sandbox engine runs LLM-generated JavaScript inside **V8 isolates** powered by `isolated-vm`. Each execution gets a fresh, ephemeral isolate — an independent V8 heap with no access to Node.js APIs, the filesystem, the network, or the host process memory. Communication between the isolate and the host occurs exclusively through a narrow message-passing bridge of serialized JSON strings and `ivm.Reference` function handles.

### Core Components

**SandboxEngine** — The coordinator. Accepts `execute` messages, acquires isolates, builds contexts, runs code, and returns `SandboxSession` objects for streaming results.

**IsolateManager** — Manages V8 isolate lifecycle with a warm pool of pre-created isolates for sub-5ms acquisition. Isolates are always disposed after execution (ephemeral guarantee — no state leakage between runs).

**ContextBuilder** — Instruments each isolate with the bridge surface: `dataProxy.request()` for fetching data from the host, `reportStatus()` for progress events, captured `console` methods, and WASM module bindings (DuckDB, GLPK, simple-statistics).

**CodeExecutor** — Compiles and runs LLM-generated JavaScript with dual timeout enforcement: V8-native timeout catches synchronous CPU loops, external `setTimeout` + `isolate.dispose()` catches async hangs. Both produce the same `SandboxTimeoutError`.

**WasmLoader** — Loads and caches WASM module binaries once at startup. Binding scripts are compiled and injected into each isolate context at build time.

### Message Protocol

All communication uses a single `SandboxMessage` discriminated union. Every message carries a `requestId` for execution-level correlation.

- **Inbound (caller to engine):** `execute` (submit code), `data_response` (fulfill a data request)
- **Outbound (engine to caller):** `status` (progress), `data_request` (isolate needs data), `result` (execution complete), `error` (execution failed)

The sandbox is data-source-agnostic. The `data_request` message carries a generic `method` + `params` that the control plane routes through the existing `ToolRegistry`. New data sources require new tools, zero sandbox changes.

### 5-Tier Resource Limits

| Tier                     | Scope                    | Key Limits                                                                | Enforcement                          |
| ------------------------ | ------------------------ | ------------------------------------------------------------------------- | ------------------------------------ |
| **1. Isolate**           | Per execution            | 256 MB heap, 30s wall-clock, 512 KB code size                             | V8 engine (hard kill on OOM/timeout) |
| **2. Bridge**            | Per execution            | 20 data requests, 50 MB inbound data, 5 MB result, 1000 status events     | Engine counters in execute() closure |
| **3. Conversation turn** | Per turn                 | 5 sandbox executions, 100 MB aggregate data, 60s aggregate wall-clock     | Orchestrator tracking                |
| **4. Per-user**          | Per user, rolling window | 5 concurrent executions, 30/min execution rate, 200/min data request rate | Redis-backed rate limiting           |
| **5. System**            | Global                   | 100 max concurrent isolates, connection pool limits                       | IsolateManager + infrastructure      |

### WASM Modules

| Module            | Purpose                                                                       | Size   |
| ----------------- | ----------------------------------------------------------------------------- | ------ |
| DuckDB-WASM       | Columnar SQL over in-memory data (pivots, aggregations, window functions)     | ~6 MB  |
| GLPK.js           | Mixed-integer linear programming (advance optimization, portfolio allocation) | ~2 MB  |
| simple-statistics | Regression, distributions, descriptive stats, outlier detection               | ~80 KB |

### Data Request Flow

```mermaid
sequenceDiagram
    participant Code as LLM-generated code<br/>(V8 isolate)
    participant Bridge as Bridge / Session
    participant Control as Control Plane
    participant Tools as ToolRegistry

    Code->>Bridge: dataProxy.request('snowflake_query', {sql})
    Bridge->>Control: data_request event
    Control->>Control: validate: allowedTools? limits? rate limit?
    Control->>Tools: execute(tool, input)
    Tools-->>Control: result
    Control->>Bridge: data_response
    Bridge-->>Code: resolve callback with data
    Note over Code: continues execution
```

## User Experience

The user never sees code. They never know an isolate was created, that JavaScript was compiled, or that WASM was initialized. They ask a question, and they get an answer.

**What the user sees for a simple computation:**

> **User:** "What would revenue look like for this contract at 15% instead of the current 12%?"
>
> **Agent:** "Based on the last 8 quarters of revenue for contract #4821, here's the comparison:
>
> | Quarter | At 12% (actual) | At 15% (projected) | Delta    |
> | ------- | --------------- | ------------------ | -------- |
> | Q1 2025 | $142,300        | $177,875           | +$35,575 |
> | Q2 2025 | $156,800        | $196,000           | +$39,200 |
> | ...     | ...             | ...                | ...      |
>
> Over the trailing 8 quarters, the 15% rate would have generated an additional $287,400 in revenue."

Behind the scenes: the agent generated JavaScript that called `dataProxy.request()` to pull revenue data, ran the arithmetic, formatted the table, and returned the result. The user received a direct answer to a direct question.

**What the user sees for a long-running computation:**
For complex analyses (optimization, large aggregations), the agent streams status updates:

> "Pulling revenue data for 50 accounts... Calculating recoupment velocity... Ranking results..."

These status events map to `reportStatus()` calls from the isolate, streamed to the client via SSE.

**What the user sees when limits are hit:**

> "I pulled the data for all 200 accounts, but the analysis exceeded the computation time limit (30 seconds). Could you narrow the request to a specific region or top 20 accounts? That would let me complete the analysis within the available compute budget."

No stack traces. No error codes. The agent explains the constraint in human terms and suggests a path forward.

## Benefits

### Business Benefits

- **Analyst productivity multiplied.** Questions that currently require 30-60 minutes of Excel work (export data, build formulas, format charts, email results) become 30-second conversations. Across a team of analysts handling dozens of these requests per day, the time savings compound dramatically.
- **Deeper insights, faster decisions.** When analysis is effortless, people analyze more. Analysts will explore scenarios they would never have bothered building spreadsheets for — the marginal cost of asking "what if?" drops to zero.
- **Competitive differentiation.** Most AI agents in the royalties space can answer questions about data. None can compute, optimize, and visualize. This positions ows-coda as an analytical platform, not a chatbot.
- **Reduced escalation to data science.** Questions like "what's the optimal advance allocation?" currently require a data science engagement with weeks of lead time. The agent answers them in seconds using GLPK.

### User Benefits

- **No more copy-paste to Excel.** The number one workflow friction — exporting data from the agent to do actual work on it — disappears.
- **Complete answers to complex questions.** The agent answers "which accounts are underperforming?" not with raw data, but with a ranked, scored, flagged analysis.
- **Compound questions in a single turn.** Pull, transform, compute, rank, and present — all in one response instead of five separate queries.

### Engineering Benefits

- **Extensible analytical platform.** The WASM injection architecture means new capabilities (charting, forecasting, NLP) are configuration changes, not architectural redesigns.
- **Hexagonal library design.** The sandbox is a standalone package (`@coda/sandbox`) testable in-process with Jest. The gRPC wrapper (sub-project #2) is a thin adapter. The message protocol maps directly to protobufs.
- **Security by architecture, not convention.** V8 isolates provide memory-gapped sandboxing. The security model is structural (empty context, nothing injected except explicit bridge functions), not a list of checks that could be forgotten.

## Costs

### Engineering Effort

The sandbox engine is sub-project #1 of 4 in the Agent Sandbox & RPC Bridge initiative:

| Sub-Project                  | Scope                                                                                                        | Estimated Effort           |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | -------------------------- |
| #1 Sandbox Engine (this PRD) | `@coda/sandbox` package: V8 isolates, message-passing, WASM injection, 5-tier limits                         | Core engine implementation |
| #2 gRPC Bridge               | Protobuf schemas, gRPC server/client, adapter wiring to sandbox API                                          | Transport layer            |
| #3 Message Dispatcher        | Control plane security gatekeeper, tool registry integration, conversation turn budgeting, Redis rate limits | Integration layer          |
| #4 Docker Compose & SSE      | Local dev environment, network isolation, SSE status streaming to React client, abort-on-disconnect          | Delivery layer             |

### Infrastructure Costs

- **Memory:** Each active isolate consumes up to 256 MB of V8 heap. At 10 concurrent isolates (small production), this requires ~2.5 GB of headroom for V8 heaps alone, plus the host Node.js process overhead.
- **CPU:** V8 isolates use real CPU time on worker threads. Heavy mathematical workloads can saturate individual cores.
- **WASM initialization:** DuckDB-WASM initialization takes 100-500ms per execution. This is the most expensive per-execution cost and dominates latency for DuckDB-dependent workloads.

### Maintenance Burden

- **Security surface area.** The sandbox adds a code execution surface that must be monitored, tested, and audited. The 5-tier limit system requires ongoing tuning as usage patterns emerge.
- **Native addon dependency.** `isolated-vm` is a native Node.js addon requiring compilation. It ties the project to specific Node.js versions and build toolchains.
- **WASM module vetting.** Each WASM module and its JavaScript bindings must be treated as trusted code. New modules require security review before inclusion.

### Opportunity Cost

Engineering time spent on the sandbox engine is time not spent on other agent capabilities. The 4-sub-project scope represents a significant investment in infrastructure over features. The justification is that this infrastructure unlocks an entire category of features (computation, visualization, optimization) that are impossible without it.

## Dependencies

| Dependency                          | Type                         | Risk                                                                                              |
| ----------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------- |
| `isolated-vm` ^5.0.0                | npm package (native addon)   | Core runtime. Well-maintained, battle-tested. Requires native compilation.                        |
| `duckdb-wasm`                       | WASM binary + JS bindings    | Columnar SQL capability. ~6 MB binary baked into Docker image.                                    |
| `glpk.js`                           | WASM binary + JS bindings    | Linear programming solver. ~2 MB binary.                                                          |
| `simple-statistics`                 | JS library                   | Statistical functions. ~80 KB, pure JS.                                                           |
| Node.js >= 24                       | Runtime                      | Required by existing project. `isolated-vm` compatibility verified.                               |
| gRPC bridge (sub-project #2)        | Internal dependency (future) | Sandbox works in-process without it; gRPC is a transport adapter.                                 |
| Message dispatcher (sub-project #3) | Internal dependency (future) | Control plane integration: tool policy enforcement, Redis rate limits, conversation turn budgets. |
| Docker Compose (sub-project #4)     | Internal dependency (future) | Local dev environment, network isolation, SSE streaming.                                          |

## Risks & Mitigations

### Security Risks

| Risk                                | Severity | Mitigation                                                                                                                                                                                                                                     |
| ----------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Code injection / isolate escape** | Critical | V8 isolates are memory-gapped. Context starts empty — no `fetch`, `fs`, `process`, `require`, `eval` (eval runs in isolate context, cannot escape), `Function` constructor (evaluates in isolate scope). The isolate itself IS the validation. |
| **Resource exhaustion (CPU)**       | High     | Dual timeout: V8-native timeout catches sync loops; external `isolate.dispose()` catches async hangs. Both enforce the same `timeoutMs` ceiling.                                                                                               |
| **Resource exhaustion (memory)**    | High     | V8 enforces `memoryMb` as a hard heap ceiling. OOM kills the isolate, not the host.                                                                                                                                                            |
| **Request amplification**           | High     | 5-tier limit stack. Per-execution: 20 data requests, 50 MB data. Per-turn: 5 executions, 100 MB aggregate. Per-user: 30 executions/min, 200 data requests/min (Redis).                                                                         |
| **Data exfiltration**               | Medium   | No network APIs injected. Tool policy allowlist restricts which data sources the isolate can access. Read-only mode prevents mutating tools. All data requests audit-logged.                                                                   |
| **WASM module compromise**          | Medium   | WASM bindings are treated as trusted code, vetted before inclusion. No dynamic WASM loading — binaries baked into Docker image.                                                                                                                |
| **V8 engine crash**                 | Low      | Single-process model means a V8 crash (extremely rare with isolated-vm) would bring down the host. Future mitigation: `worker_threads` isolation.                                                                                              |

### Technical Risks

| Risk                                | Severity | Mitigation                                                                                                                                                                              |
| ----------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **DuckDB-WASM inside isolate**      | Medium   | Open question: can DuckDB-WASM be instantiated inside a memory-gapped isolate, or does it need a shared ArrayBuffer workaround? Requires prototyping.                                   |
| **WASM initialization latency**     | Medium   | DuckDB-WASM takes 100-500ms to initialize per execution. Future mitigation: pre-initialize WASM in warm pool isolates.                                                                  |
| **Warm pool sizing**                | Low      | Optimal pool size requires benchmarking with real workloads. Start with conservative defaults (2-5) and tune based on production metrics.                                               |
| **Promise/async bridge complexity** | Medium   | Promises cannot cross the isolate boundary. Callback-based async bridge pattern handles this transparently, but adds implementation complexity. Thoroughly tested in integration suite. |

### Operational Risks

| Risk                       | Severity | Mitigation                                                                                                                                                                                                 |
| -------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Limit tuning**           | Medium   | Default limits are conservative. Production usage will reveal whether 30s timeout, 256 MB heap, 20 data requests per execution are appropriate. All limits are configurable per-request.                   |
| **Monitoring blind spots** | Medium   | Execution stats (wallMs, cpuMs, heapMb, dataRequestCount) are returned with every result. Audit logging captures every data request. `IsolateManager` exposes `activeCount` and `poolSize` for dashboards. |

## Timeline & Milestones

### Sub-Project #1: Sandbox Engine (this PRD)

| Phase                             | Deliverable                            | Key Work                                                                                                                                                                              |
| --------------------------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Phase 1: Foundation**           | Package scaffold + protocol types      | `package.json`, `tsconfig.json`, `jest.config.json`, workspace integration, `SandboxMessage` union, `JsonSerializable`, limit types, default constants                                |
| **Phase 2: Session & Errors**     | Event streaming + error hierarchy      | `SandboxSession` (async queue, pending request map, send/abort/close lifecycle), `SandboxError` class hierarchy (5 subclasses), bridge barrel export                                  |
| **Phase 3: Execution Layer**      | V8 isolate management + code execution | `IsolateManager` (warm pool + ephemeral guarantee), `ContextBuilder` (bridge injection + WASM setup), `CodeExecutor` (compile + run + dual timeout), `WasmLoader` (disk read + cache) |
| **Phase 4: Engine + Integration** | Coordinator + end-to-end tests         | `SandboxEngine` (submit/shutdown orchestration), integration tests (full submit-to-result flow), security red team tests (OOM, timeout, escape, amplification, flooding)              |

### Sub-Project #2: gRPC Bridge

Protobuf schemas mirroring the `SandboxMessage` union. gRPC server wrapping `SandboxEngine`. Bidirectional streaming for event delivery. Thin adapter — no business logic.

### Sub-Project #3: Message Dispatcher & Control Plane

Security gatekeeper between the agent orchestrator and the sandbox. Tool policy enforcement (allowlist, read-only check). Conversation turn budget tracking (Tier 3). Redis-backed per-user rate limiting (Tier 4). Integration with existing `ToolRegistry` for routing `data_request` messages.

### Sub-Project #4: Docker Compose & Status Streaming

Local development environment with network isolation. SSE status streaming from sandbox to React client. Abort-on-disconnect (if client drops SSE connection, in-flight executions are aborted via `session.abort()`).

## Open Questions

- **DuckDB-WASM in memory-gapped isolates:** Can DuckDB-WASM be instantiated inside an `isolated-vm` isolate, or does it require shared ArrayBuffer support that the memory gap prevents? This is the highest-risk technical unknown and needs prototyping before Phase 3.
- **Optimal warm pool size:** What is the right `warmPoolSize` for production? Needs benchmarking with real analytical workloads to balance cold-start latency against idle memory consumption.
- **Multi-tier warm pool:** Warm pool isolates are pre-created with default memory limits. Should the pool support multiple size tiers (e.g., 128 MB and 512 MB pools) for workloads with different memory profiles?
- **Worker thread isolation:** Should each isolate run in a separate `worker_thread` to contain V8 crashes? This adds complexity but eliminates the single-process risk. Evaluate based on production stability data.
- **Chart generation:** Which WASM-compatible charting library should be added for inline visualization? This is outside the current scope but is a high-value follow-on capability.
- **TypeScript support:** The LLM generates JavaScript (V8 does not execute TypeScript natively). Should a transpilation step (e.g., `esbuild.transform`) be added before injection to allow TypeScript generation? Evaluate based on LLM output quality with each language.
- **Per-tenant rate limiting:** Current rate limiting is per-user and per-system. Should per-tenant (organizational) limits be added for multi-tenant deployments?
