Status: v0.1 final / implementation-planning specification
Scope: agent-facing constrained generation, fast checked interpretation, sandboxed behavioral feedback, backend parity, and review-first integration for RSScript.
This document defines the RSScript-specific feedback loop for AI-generated code. It combines constrained generation and fast interpretation into one design for making generated RSScript faster to produce, faster to validate, and easier to review.
The design has two main components:
- A generation oracle that exposes parser, checker, type, effect, signature, and project-symbol knowledge over incomplete RSScript programs.
- An interpreter that executes checked RSScript directly for fast behavioral feedback before final backend validation.
The goal is not to make RSScript implicit, magical, or autocomplete-driven. The goal is to make correct, explicit RSScript cheap to generate while preserving the existing review-first model.
RSScript has almost no model pretraining prior. A prompt-sized guide can teach the language, but it cannot reliably prevent common generated-code failures:
- missing named arguments
- missing
read/mut/take - invalid receiver methods
- unbound names
- wrong error type after
? - slow behavioral feedback when every run goes through the Rust compiler
The intended loop is:
AGENT.md raises the model's prior
generation oracle prevents many invalid prefixes while code is emitted
rss check verifies type/effect contracts
rss run --vm gives ms-level behavioral feedback
rss run / Rust backend remains the final execution authority
review / REIR remains the product boundary
This is not autocomplete for humans. It is an agent-facing compiler oracle and a fast runtime probe. The goal is to remove low-value generate/check/repair cycles without weakening RSScript's explicitness or review boundary.
RSScript should make correct code cheap to generate without making behavior implicit.
Prompting teaches.
Constrained generation steers.
Checking proves the contract.
The interpreter probes behavior quickly.
The Rust backend defines final behavior.
Review remains the bottleneck.
Constrained generation must not become hidden inference. It may offer legal next tokens, names, parameter labels, variants, expected types, and required effects, but the emitted source is still ordinary RSScript. The source remains reviewable without knowing the generation machinery.
The interpreter must not become a second semantics. It executes a supported subset of checked HIR for fast feedback and is continuously checked against the lowered Rust backend.
The following invariants are non-negotiable:
- The oracle never emits source transformations invisible in RSScript source.
Deadmust be sound;Incompletemay be conservative.rss_evalonly executes checked HIR.- Agent-facing eval never uses
RealHostby default. - Unsupported interpreter constructs fail closed with diagnostics.
- The Rust backend is the semantic authority.
- Generated artifacts have freshness guards against parser/runtime ABI drift.
- Review/REIR remains the product boundary; generation and eval only reduce noise.
- Interpreter results are probes, not final correctness guarantees.
- Real host execution is always explicit and policy-controlled.
- Budget exhaustion, partial project context, or incomplete semantic domains degrade to
Incomplete, not unsoundDead.
The oracle exposes the checker over an incomplete program. It answers two questions:
Can this prefix still become valid RSScript?
What RSScript tokens/names are legal next?
It works at the RSScript-token frontier, not directly at the model's BPE token level. A model adapter can later turn legal RSScript continuations into a logit mask, but the first useful version is an agent tool.
The oracle should understand:
- lexical prefixes
- parser states
- incomplete expressions
- incomplete call sites
- in-scope locals and parameters
- imports and package symbols
- core functions and types
- receiver types
- method signatures
- named arguments
- required effects
- expected expression types
- sum variants for known scrutinee types
The interpreter executes checked RSScript without lowering to a temporary Rust package and invoking cargo run.
rss check type/effect feedback in ms
rss run --vm behavioral feedback in ms
rss run backend-authoritative execution in seconds
It is a tree-walker over checked HIR/user code and dispatches built-in calls through the same runtime crate used by lowered Rust.
The interpreter exists to accelerate feedback. It does not replace the checker, the Rust backend, or review.
Public module:
pub mod generate;The oracle must run against a loaded compiler/project context, not against an isolated text buffer. Prefix validity, semantic continuations, and Dead soundness depend on the same package, import, core, and symbol visibility rules used by rss check.
#[derive(Debug)]
pub struct GenerateContext<'a> {
pub file: &'a str,
pub partial_source: &'a str,
pub project: &'a ProjectContext,
pub symbol_completeness: SymbolCompleteness,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SymbolCompleteness {
Complete,
Partial,
}
#[derive(Debug, Clone)]
pub struct ContinuationOptions {
pub max_returned_names: usize,
pub max_probe_candidates: usize,
pub include_snippets: bool,
}
pub fn prefix_status(ctx: &GenerateContext<'_>) -> PrefixStatus;
pub fn valid_continuations(
ctx: &GenerateContext<'_>,
options: ContinuationOptions,
) -> Continuations;Convenience wrappers are acceptable only inside a fully loaded compiler session:
pub fn prefix_status_in_session(
session: &CompilerSession,
file: &str,
partial_source: &str,
) -> PrefixStatus;
pub fn valid_continuations_in_session(
session: &CompilerSession,
file: &str,
partial_source: &str,
options: ContinuationOptions,
) -> Continuations;The file argument identifies a file inside that session. It must not imply that Dead can be proven from a single standalone text buffer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PrefixStatus {
Complete,
Incomplete,
Dead { at: Span, reason: String },
}Meaning:
Complete: the whole top-level RSScript program is complete and passes the supported checks.Incomplete: the source is truncated, the local construct is unfinished, or the oracle cannot soundly prove that the prefix is dead.Dead: the source has committed to something invalid that cannot be fixed by appending more tokens.
Complete is program-level. It does not mean “this local expression may stop.” A prefix that is syntactically valid at a local grammar frontier but still inside an unfinished function body, block, call, match, or file is still Incomplete.
Continuations.may_stop is local. It means the current syntactic slot may be ended, not that the whole file is complete.
The first version should be conservative. Treat ambiguous truncation as Incomplete; only return Dead for clear committed errors such as unknown callee, unknown argument, missing required effect after a completed argument, or receiver method that cannot exist.
Dead is allowed to under-report; it must never over-report.
If PrefixStatus::Dead is returned, no valid RSScript token stream appended to
this exact prefix can make the program valid in the current project context.
Deadness is context-sensitive. The oracle may return Dead for semantic errors only when the relevant semantic domain is complete.
Examples of domains that must be complete before returning Dead:
unknown callee:
all visible locals, params, imports, package symbols, and core symbols
unknown receiver method:
receiver type and all visible methods/extensions for that type
unknown argument label:
resolved callee signature and all remaining parameter labels
unknown variant:
resolved scrutinee type and all variants for that type
wrong required effect:
resolved parameter signature and committed argument expression
If the project context, import graph, package index, core index, receiver type, or callee signature is incomplete, the oracle must return Incomplete rather than Dead.
A trailing partial token keeps the prefix Incomplete only if it is a prefix of at least one legal token in the current lexical, syntactic, or semantic domain. If it is not a prefix of any legal token, and appending further token text cannot make it legal, Dead is sound.
Examples:
Path.exiThis is Incomplete if exists is a valid method on Path.
File.read_bytes(paThis is Incomplete if path is a valid remaining argument label.
Path.nonexistentThis may be Dead only if receiver type resolution is complete and no valid method for that receiver has nonexistent as a prefix.
File.read_bytes(path: "x")This may be Dead only if the callee signature is resolved and the argument expression has committed without the required effect.
unknown_fn(This may be Dead only if visible symbol resolution is complete.
If symbol completeness is Partial, unknown-name diagnostics may still be reported as ordinary diagnostics, but prefix_status must degrade to Incomplete.
#[derive(Debug, Clone)]
pub struct Continuations {
pub keywords: Vec<&'static str>,
pub symbols: Vec<&'static str>,
pub literals: Vec<LiteralClass>,
pub names: Vec<Completion>,
pub expected_type: Option<ExpectedType>,
pub may_stop: bool,
pub truncated: bool,
pub total_name_candidates: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct Completion {
pub text: String,
pub insert_text: String,
pub replace: TextRange,
pub kind: CompletionKind,
pub signature: Option<String>,
pub required_effect: Option<Effect>,
pub result_type: Option<TypeRef>,
pub commit: CommitBehavior,
}
#[derive(Debug, Clone)]
pub struct ExpectedType {
pub display: String,
pub type_id: Option<TypeId>,
pub accepts: Vec<TypeId>,
}
#[derive(Debug, Clone)]
pub struct TypeRef {
pub display: String,
pub type_id: Option<TypeId>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TextRange {
pub start: usize,
pub end: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypeId {
pub canonical: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Effect {
Read,
Mut,
Take,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CommitBehavior {
InsertName,
ReplacePartialToken,
InsertArgumentWithEffect,
InsertSnippet,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompletionKind {
Local,
Param,
Type,
Function,
Method,
ArgName,
Variant,
}The high-value completions are semantic, not UI-oriented:
- after
Path.: only validPathmethods - after
path.: methods for the inferred receiver type - after
File.read_bytes(: only remaining parameter names - after
path:: the required effect, usuallyread - in expression position: in-scope locals/params and valid literals
- after
match value {: valid variants for the scrutinee type where known
This should be exposed as an agent tool named something like rss_generate, not as human autocomplete. Human-facing completion is explicitly not a goal.
Continuations.expected_type describes what the current expression context wants.
Completion.result_type describes what a candidate produces.
may_stop is a local grammar property. It does not imply PrefixStatus::Complete.
truncated means the returned candidate list is intentionally capped by ContinuationOptions. Truncation is allowed for usability and performance, but truncated candidate enumeration must never be used to prove Dead.
total_name_candidates is best-effort metadata. It may be omitted when computing the full count would require expensive exhaustive enumeration.
text and insert_text are intentionally separate.
Path.from_A method completion may return:
text=from_string
insert_text=from_string
replace=<range of from_>
commit=ReplacePartialToken
The applied result is:
Path.from_stringAfter:
Path.from_string(An argument completion may return:
text=value
insert_text="value: read "
replace=<empty range at cursor>
commit=InsertArgumentWithEffect
The applied result is:
Path.from_string(value: read For expression-position candidates, required_effect means the effect that must be applied at the call site or argument site. It does not mean the returned value itself carries that effect at runtime.
To apply a completion, replace:
partial_source[completion.replace.start..completion.replace.end]
with:
completion.insert_text
The resulting source must be either a valid prefix or a complete checked program according to prefix_status.
Agents must not append insert_text manually when replace is non-empty. The edit range is authoritative.
The oracle should prefer source edits that keep the emitted program ordinary RSScript. Snippet-like completions are allowed only when the inserted source is explicit and reviewable.
pub struct Generator {
// lexer/parser/checker frontier
}
impl Generator {
pub fn new(session: Arc<CompilerSession>, file: FileId, initial_source: String) -> Self;
pub fn push(&mut self, token: &str) -> PrefixStatus;
pub fn peek(&self, token: &str) -> PrefixStatus;
pub fn continuations(&self, options: ContinuationOptions) -> Continuations;
pub fn checkpoint(&self) -> GeneratorState;
pub fn restore(&mut self, state: GeneratorState);
}The stateless API is authoritative for the early implementation. The Generator handle is the production shape for speculative decoding, checkpoint/restore, and backtracking.
Generator owns the mutable source prefix and frontier state. It keeps a shared compiler session plus a stable file identity instead of storing a borrowed GenerateContext; otherwise checkpoint/restore and long-lived speculative decoding would have unclear ownership.
P0/P1 may reparse prefixes. P5 should keep an incremental frontier.
The P1 oracle must not implement continuations as “try every visible symbol through a full parser/checker pass.”
P1 may use generate-and-test, but it must be bounded and context-pruned.
Candidate construction must be staged:
1. Determine syntactic slot.
2. Determine semantic domain.
3. Apply partial-token prefix filter.
4. Apply expected-type filter where available.
5. Apply receiver/callee/signature filter where available.
6. Rank or bucket candidates.
7. Probe only the bounded candidate set.
The oracle should avoid adding irrelevant global names to the probe set. Examples:
after receiver dot:
only methods valid for the resolved receiver type
inside call argument label position:
only remaining argument labels for the resolved callee
after an argument label requiring an effect:
only legal effect keywords and expressions compatible with the parameter type
in type position:
types only
in expression position:
locals, params, literals, variants, constructors, and functions compatible
with the expected type where known
P1 must define a probe budget:
max_returned_names:
maximum number of name completions returned to the agent
max_probe_candidates:
maximum number of candidate edits that may be parser/checker-probed
If the semantic domain exceeds the budget, the oracle should:
return the best bounded continuation set
set truncated = true
set total_name_candidates where cheap to compute
avoid using the truncated set to prove Dead
return Incomplete rather than Dead when exhaustive proof is required
Generate-and-test is therefore a filtering and validation strategy, not an unbounded N candidates × full reparse algorithm.
P1 should cache the following per prefix or parser frontier:
lexed token frontier
parser recovery state
scope lookup result
receiver type
callee signature
expected type
visible method set
remaining argument labels
P5 incremental generation improves this further, but P1 must already have bounded candidate enumeration and must not rely on P5 for basic performance safety.
L0 Lexical
Is the current partial token a valid prefix of a keyword, identifier, number,
or string?
L1 Syntactic
Which token kinds can grammatically appear next?
L2 Semantic
Which concrete names, methods, variants, parameter labels, and effect
keywords are valid in this scope and type context?
L0/L1 reduce syntax noise. L2 is where RSScript has leverage over a plain CFG: it knows .rssi signatures, receiver types, required effects, expected types, and in-scope names.
The oracle should not duplicate long-term language semantics. It should reuse the parser/checker wherever possible and rely on generated artifacts where duplication would otherwise drift.
Do not start with a full BPE logit-mask implementation.
Start with bounded, context-pruned generate-and-test over a small candidate set:
candidate set =
keywords valid for the syntactic slot
symbols valid for the syntactic slot
literal classes valid for the expected type
in-scope names from symbol_index after scope filtering
core/package function names after domain and expected-type filtering
method names from the resolved receiver type
parameter labels from the resolved callee signature
required effect keywords from the parameter signature
variants from known sum types
For each candidate in the bounded probe set, tentatively apply the candidate edit and run parser/checker validation. Keep candidates that do not introduce a committed error.
This reuses the checker as the authority and avoids a second grammar in the first version.
The MVP must not attempt unbounded exhaustive probing in large scopes. If the candidate domain is too large, it returns a truncated candidate list and degrades proof-oriented status to Incomplete where needed.
Later, generate a decode grammar from the same source of truth as the parser and editor grammar. The generated grammar must have a freshness-guard test.
P0/P1 are not complete without golden prefix tests.
The suite must cover:
receiver methods:
offer valid methods after a typed receiver
do not offer invalid methods
preserve partial receiver-method prefixes
do not prove Dead from incomplete receiver/method context
call arguments:
offer remaining named args only
offer required effect after an arg label
reject committed missing/wrong effects
do not prove Dead from incomplete callee signatures
prefix status:
never return Dead for a legal partial token prefix
return Dead for impossible partial tokens only when domain is complete
return Incomplete for unmatched delimiters
return Incomplete under partial symbol context
return Complete only for a full top-level checked program
typing:
include expected type in expression position
include result type for completions where known
offer valid variants for known match scrutinee types
application:
applying completion.replace + completion.insert_text yields a valid prefix
completions do not require hidden source transformations
performance:
large visible scopes respect max_probe_candidates
truncation is reported explicitly
truncated candidate sets are not used for Dead proof
Representative negative prefixes:
Path.nonexistent
File.read_bytes(path: "x")
unknown_fn(
match option_value { Ok(Representative incomplete prefixes:
Path.exi
File.read_bytes(pa
Path.from_string(value: rea
match option_value {Prefix:
fn main() -> Result<Unit, FileError> {
let path = Path.Continuations:
names:
from_string Method Path.from_string(value: read String) -> fresh Path
exists Method Path.exists(path: read Path) -> Bool
...
may_stop: false
truncated: false
Path.nonexistent is not offered.
After:
Path.from_string(Continuations:
names:
value ArgName required_effect=read
The applied completion is:
Path.from_string(value: read The agent is steered toward:
Path.from_string(value: read "README.md")The two common RSScript errors, missing argument labels and missing effects, become hard to emit in generated code.
Public module:
pub mod interp;Public surface:
pub struct EvalOptions {
pub host: HostMode,
pub entrypoint: String,
pub args: Vec<String>,
}
pub enum HostMode {
Real,
Sandbox(SandboxConfig),
Denied,
}
pub struct EvalResult {
pub status: EvalStatus,
pub value: Option<EvalValue>,
pub stdout: String,
pub stderr: String,
pub logs: Vec<String>,
pub diagnostics: Vec<Diagnostic>,
pub execution: ExecutionState,
}
pub enum EvalStatus {
Returned,
CheckFailed,
Unsupported,
RuntimeFault,
HostDenied,
ResourceLimitExceeded,
}
pub enum ExecutionState {
NotStarted,
Completed,
Partial,
}
pub fn eval_file(path: &Path, options: EvalOptions) -> EvalResult;
pub fn eval_source(file: &str, source: &str, options: EvalOptions) -> EvalResult;eval_source and eval_file must check the program before execution. If checking fails, they return EvalStatus::CheckFailed and do not execute user code.
The status field is authoritative. value: Option<EvalValue> must not be used to infer execution state.
Status/value conventions:
Returned:
value = Some(returned value)
execution = Completed
CheckFailed:
value = None
execution = NotStarted
Unsupported:
value = None
execution = NotStarted when discovered by preflight
execution = Partial only if support is discovered dynamically and prior
effects already occurred
RuntimeFault:
value = None
execution = Partial unless the fault occurs before user execution
HostDenied:
value = None
execution = Partial if prior effects occurred, otherwise NotStarted
ResourceLimitExceeded:
value = None
execution = Partial
A successful Unit return is represented as:
EvalResult {
status: EvalStatus::Returned,
value: Some(EvalValue::Unit),
execution: ExecutionState::Completed,
...
}EvalStatus does not include backend divergence. Divergence is a parity-harness result, not a normal eval result.
CLI:
rss test # may use interpreter by default once parity is good
Agent tool:
rss_eval(path, entrypoint, input, host=sandbox)
Agent-facing eval must default to sandbox mode and must not silently fall back to real host execution.
enum Value {
Unit,
Int(i64),
Bool(bool),
Str(String),
Bytes(Vec<u8>),
List(Vec<Value>),
Map(IndexMap<ValueKey, Value>),
Struct(StructValue),
Variant { tag: String, payload: Option<Box<Value>> },
Managed(Rc<RefCell<Object>>),
Closure(Rc<ClosureValue>),
Native(NativeHandle),
}Operational rules:
read x pass an immutable view / clone shared handle
mut x mutate the stored binding/object
take x move the value; the source binding is dead
manage x wrap a local value into Managed
fresh x no runtime operation; freshness is statically checked
? on Err/None, return immediately from the enclosing function
local x observable result only; local performance optimizations are ignored
The interpreter trusts the checker for legality. It does not re-run the ownership/effect checker, but it must still enforce runtime consequences of checked ownership and effect operations:
takemust make the source binding unavailable.mutmust mutate the correct binding/object.readmust not expose mutation capability.?must release resources consistently with backend behavior.- observable effects must match backend behavior under the same host.
Built-ins must use the same runtime crate as lowered Rust.
RSScript call
-> checked HIR
-> runtime_abi lookup
-> marshal Value arguments
-> call rsscript_runtime function
-> marshal return Value
The interpreter dispatcher is a finite mapping from .rssi signatures and runtime_abi.rs.
The pure smoke-test MVP may handwrite a tiny dispatcher for core types. Broad stdlib coverage must not rely on handwritten marshalling. P4 must generate the dispatcher from the authoritative runtime ABI table plus .rssi signatures, with a freshness-guard test.
Generated dispatch should validate:
- callee identity
- parameter count
- parameter names
- required effects
- argument value marshalling
- return value marshalling
- host capability requirements
- diagnostic mapping
Generated dispatcher artifacts must fail tests when runtime_abi.rs, .rssi signatures, or generated marshalling tables drift.
The interpreter must not let agent probes freely touch the machine.
trait Host {
fn fs(&self) -> &dyn FsHost;
fn clock(&self) -> &dyn ClockHost;
fn random(&self) -> &dyn RandomHost;
fn net(&self) -> &dyn NetHost;
fn process(&self) -> &dyn ProcessHost;
fn env(&self) -> &dyn EnvHost;
}Modes:
RealHost: pass-through to runtime intrinsics; used for parity and explicit local execution.SandboxHost: in-memory or confined FS, denied network, denied process, fixed clock, seeded random, controlled environment.DeniedHost: all host capabilities are denied.
P2 pure interpreter may provide DeniedHost or equivalent default-deny stubs for all host capabilities. Pure core intrinsics should not need host access. If a P2 program reaches a host-dependent intrinsic, the interpreter should fail closed with Unsupported or HostDenied, depending on whether the intrinsic is outside P2 coverage or explicitly denied by host policy.
The code-agent should default to SandboxHost once agent-facing eval exists. Real host must be explicit in tool policy.
rss_eval can exist locally before the sandbox is complete, but it must not be exposed as an agent tool with real host access.
P2 pure interpreter:
no fs/net/process/env/clock/random host access
only pure core intrinsics
DeniedHost or default-deny stubs
P2 local harness:
pure interpreter execution for tests/agents
P3 agent-facing eval:
sandbox host required
real host forbidden by default tool policy
Agent-facing eval may only use SandboxHost unless a tool policy explicitly grants real host access for a trusted local run.
Sandboxing is not only about filesystem and network access. Agent-facing eval must also enforce resource limits.
Sandbox limits:
max evaluation steps / instructions
max wall time
max call depth
max heap/value size
max stdout bytes
max stderr bytes
max log bytes
max filesystem bytes
max filesystem file count
max diagnostics emitted
Resource-limit failures return:
EvalStatus::ResourceLimitExceededand emit:
RS1206 sandbox resource limit exceeded
Resource limits must fail closed. The interpreter must not continue running after a hard sandbox limit is exceeded.
P2 supports pure checked HIR execution:
- scalars:
Unit,Bool,Int,String ListMapOptionResult- structs
- sum variants
ifmatchforwhilereturnbreakcontinue- user functions
- closures only where already checked and noescape
- core pure intrinsics:
StringListMapJsonAssertLog- basic
Result/Option
P2 should avoid host-dependent APIs except where the host dependency is explicitly mocked or denied.
Before executing an entrypoint, P2 should preflight the reachable checked HIR for unsupported constructs and unsupported intrinsics where practical. Preflight-discovered unsupported constructs return Unsupported with execution = NotStarted.
P3 adds agent-facing eval under sandbox policy:
- in-memory or confined filesystem
- denied process by default
- denied network by default
- fixed clock
- seeded random
- controlled environment
- resource limits
- deterministic eval/test fixtures
P6 expands interpreter coverage:
with- resources
ResourcePool- broader file/path APIs under host policy
- resource lifecycle parity
- broader stdlib support through generated dispatcher
Async support belongs in expanded interpreter coverage after core parity is stable:
- async/await subset over
async_runtime TimerChannelStream.nextawait forwhere already supported by HIR
Unsupported constructs must emit an explicit diagnostic. They must not silently fall back to guessed behavior.
The interpreter executes checked HIR, but the runtime boundary still needs hard operational rules:
- Native dispatch must not expose mutation capability for a parameter checked as
read. mutandtakemust be enforced at the interpreter binding/object layer, even though the checker already proved they are legal.Managedaliasing must mirror the runtime'sRc<RefCell<...>>behavior, including dynamic borrow/runtime diagnostics where applicable.Nativehandles are opaque. They can only be observed or mutated through checked intrinsics and host policy.ValueKeysupports only stable, hashable, equality-stable values.Managed,Native,Closure, and other unstable identity values are forbidden as map keys unless a future spec defines canonicalization explicitly.- Resource lifecycle events are observable for parity.
with, pool leases, and early?returns must release or discard resources exactly as the backend does.- The interpreter may ignore local performance optimizations.
- The interpreter may not ignore observable resource, mutation, error, log, or host effects.
- Map iteration order is observable when code observes it through logs, output, returned lists, or host effects.
- v0.1 interpreter parity does not define new RSScript language semantics for
Mapiteration. - Map-iteration parity fixtures are unsupported until the interpreter and lowered Rust backend share an explicit ordering policy.
- If the language later specifies insertion order, the backend must use an equivalent insertion-ordered map before those fixtures count as supported.
- If the language specifies map iteration as unordered, parity fixtures must canonicalize map observations rather than asserting concrete iteration order.
This matters because:
for k in map {
Log.info(value: read k)
}has observable log order.
Current Rust lowering may use a hash map backend. Until backend and interpreter ordering are aligned, tests that depend on concrete map iteration order must be marked unsupported for strict parity.
Interpreter diagnostics should use RSScript spans directly. This is better than the Rust backend path, which needs source-map remapping.
RS1201 runtime fault
RS1202 unsupported interpreter construct
RS1203 sandbox host denial
RS1204 intrinsic marshalling failure
RS1205 interpreter/backend parity divergence
RS1206 sandbox resource limit exceeded
The diagnostic JSON shape should match existing checker diagnostics so the agent can consume rss_check, rss_eval, and rss_run uniformly.
Diagnostics should include:
- code
- severity
- primary span
- message
- structured reason/category
- host mode where relevant
- whether execution occurred
- whether execution was partial
For parity, diagnostics should be compared in normalized form:
normalized diagnostic comparison:
diagnostic code
severity
primary source span after source-map normalization
structured reason/category
User-visible message text may have separate snapshots, but semantic parity should not depend on exact prose.
The Rust backend remains authoritative.
Parity harness:
for each supported fixture:
run with interpreter under fixed host
run lowered Rust under equivalent fixed host
compare:
return value
stdout / stderr
logs according to ordering policy
normalized diagnostics
filesystem diff under fixed host
env access trace
network/process denial trace
resource lifecycle events
panic/error boundary behavior
If results diverge, the interpreter is wrong unless the fixture declares a permitted schedule-dependent ordering difference.
If a construct is unsupported, the interpreter emits an unsupported diagnostic and the fixture is not counted as a parity success.
Parity should be fixture-driven and explicit. A fixture should declare:
- required interpreter feature set
- host mode
- input files
- environment values
- clock seed/time
- random seed
- scheduler mode, if async
- ordering policy for logs/traces
- expected stdout/stderr/logs
- expected return value
- expected filesystem diff
- expected diagnostics
The parity harness is the primary defense against interpreter drift.
Backend divergence is reported by the parity harness, not by ordinary eval.
pub struct ParityResult {
pub status: ParityStatus,
pub interpreter: EvalResult,
pub backend: BackendRunResult,
pub diagnostics: Vec<Diagnostic>,
}
pub enum ParityStatus {
Match,
Diverged,
IneligibleUnsupported,
IneligibleNondeterministic,
}ParityStatus::Diverged emits RS1205 interpreter/backend parity divergence.
Not every observable event has the same ordering guarantee.
The parity harness must classify observations:
strictly ordered:
stdout bytes
stderr bytes
logs emitted by a single sequential task
resource lifecycle events with direct happens-before relation
happens-before ordered:
events across async tasks where the program establishes ordering through
await, channel send/receive, join, or other synchronization
unordered / schedule-dependent:
logs or side effects from concurrent tasks without a program-level ordering
guarantee
Strictly ordered observations must match exactly.
Happens-before ordered observations must preserve the same causal order, but unrelated events may interleave differently.
Unordered schedule-dependent observations must be compared as a multiset, canonicalized trace, or fixture-specific predicate.
Async parity must not require the interpreter tree-walker to reproduce the exact byte-level scheduling of the lowered Rust runtime unless both are running under an explicitly deterministic scheduler.
For async fixtures, parity must declare one of:
deterministic scheduler:
interpreter and backend use equivalent deterministic scheduling
schedule-insensitive assertion:
fixture compares only values/events whose order is specified by the program
explicit nondeterminism:
fixture is not eligible for strict parity
P6 async support is not complete until async parity has a scheduler or trace-equivalence model that makes tests satisfiable.
The code-agent should use the tools in this order:
1. read context and AGENT.md
2. generate a small function or file section
3. rss_generate for local legal continuations when uncertain
4. rss_check for contract validity
5. rss_eval for behavior under sandbox
6. rss_run only for final backend validation or unsupported interpreter cases
7. review / REIR for the final change boundary
The agent should prefer smaller edits because the generation oracle and interpreter both work best on tight scopes. Large rewrites should be decomposed into functions with check/eval after each one.
The agent should treat oracle and eval output as feedback, not as proof of correctness. Final changes still require checker/backend validation and review.
Agent behavior rules:
Prefer explicit source over clever generation.
Use rss_generate when uncertain about syntax, names, labels, effects, or variants.
Use rss_check after each meaningful generated unit.
Use rss_eval for fast sandboxed behavior checks.
Use rss_run for backend-authoritative final validation.
Do not use RealHost unless explicitly permitted by tool policy.
Do not bypass review / REIR.
Deliverables:
token frontier / partial token model
GenerateContext
ProjectContext integration
SymbolCompleteness handling
prefix_status
Dead soundness rules
parser recovery / hole representation
golden prefix tests
P0 is complete when:
Completeonly returns for a full top-level checked program.Incompletehandles truncation and unmatched delimiters conservatively.Deadis sound.- partial token prefixes are preserved.
- partial symbol context degrades to
Incomplete. - golden tests cover positive, incomplete, and dead prefixes.
Deliverables:
valid_continuations
receiver method names
argument labels and required effects
in-scope locals
expected type metadata
completion result type metadata
completion application contract
bounded candidate enumeration
scope/domain pruning
partial-token prefix filtering
expected-type filtering where available
probe budget
truncated continuation metadata
agent tool: rss_generate
P1 is complete when:
- valid receiver methods are offered.
- invalid receiver methods are not offered when the domain is complete.
- remaining named arguments are offered.
- required effects are offered.
- completion edits apply cleanly.
- expected type and result type metadata are available where known.
- candidate probing is bounded.
- large scopes do not cause unbounded
N × full reparsebehavior. - truncation is explicit.
- budget exhaustion degrades to
Incomplete, not unsoundDead. rss_generateis useful to an agent without human autocomplete UI assumptions.
Deliverables:
checked HIR tree walker
scalar/List/Map/Result/Option/control flow/user fn support
String/List/Json/Assert/Log intrinsics
EvalStatus/value convention
ExecutionState
stderr capture
DeniedHost or default-deny host stubs
map iteration parity policy
Harness: pure interpreter execution
P2 is complete when:
- pure checked programs execute without Rust lowering.
- unsupported constructs fail closed.
- unsupported constructs are preflighted where practical.
- check failures do not execute.
- runtime faults have diagnostics.
- local pure-only CLI usage is possible.
- host-dependent APIs fail closed.
- eval status/value conventions are implemented.
- map iteration behavior is either matched with backend or unsupported for parity fixtures.
- no agent-facing real host eval is exposed.
Deliverables:
in-memory/confined FS
denied process/net by default
fixed clock and seeded random
controlled environment
resource limits
agent tool: rss_eval
deterministic eval/test fixtures
P3 is complete when:
- agent-facing eval requires sandbox host by default.
- real host access is policy-gated.
- resource limits are enforced.
- host denial emits diagnostics.
- deterministic fixtures are reproducible.
Deliverables:
generated marshalling from runtime_abi + .rssi
freshness guard
parity fixtures for supported intrinsics
host capability annotations
diagnostic mapping
P4 is complete when:
- broad stdlib coverage no longer depends on handwritten marshalling.
- ABI/signature drift fails tests.
- interpreter intrinsic calls use generated dispatcher metadata.
- supported intrinsic fixtures pass parity.
Deliverables:
Generator handle
checkpoint/restore
incremental lexer/parser/checker frontier
candidate scoring metadata
speculative decoding support
P5 is complete when:
- generator state can be checkpointed and restored.
- candidate probing does not require full reparsing in common cases.
- speculative decoding can use the oracle efficiently.
Deliverables:
resources
ResourcePool
broader stdlib
broader file/path APIs under host policy
resource lifecycle parity
async parity policy
deterministic scheduler or trace-equivalence model
schedule-aware log comparison
async subset where stable
P6 is complete when:
- expanded constructs are either supported with parity or rejected with explicit diagnostics.
- resource lifecycle behavior matches backend behavior.
- supported stdlib surface has generated dispatcher coverage.
- async fixtures do not rely on impossible exact runtime scheduling parity.
- concurrent observations are compared according to declared ordering guarantees.
Deliverables:
decode grammar generated from parser/keyword source
freshness-guard tests
snapshot tests
editor grammar alignment where applicable
P7 is complete when:
- decode grammar is generated, not manually duplicated.
- grammar drift fails tests.
- grammar can support later model-runtime adapters.
Deliverables:
BPE token healing
logit masks for llama.cpp/vLLM/etc.
speculative decoding integration
adapter-level tests
P8 is complete when:
- legal RSScript continuations can be translated into model-token constraints.
- token healing handles BPE boundary issues.
- adapters remain downstream of the RSScript-token oracle.
- No human autocomplete.
- No hidden type/effect inference in emitted source.
- No correctness guarantee beyond checker/eval/review.
- No replacement for Rust backend execution authority.
- No performance benchmarking through the interpreter.
- No unrestricted real-host execution in agent default mode.
- No second hand-written language semantics.
- No broad stdlib interpreter coverage before generated ABI marshalling.
- No silent fallback from unsupported interpreter constructs to guessed behavior.
- No unbounded candidate probing in the oracle MVP.
- No
Deadproof from partial project context or truncated candidate lists. - No strict async ordering parity for programs with schedule-dependent behavior.
Some invalid programs require non-local context.
Mitigation:
conservative Incomplete
sound Dead only
later backtracking
final rss check
Generate-and-test can become expensive if every visible name, package function, and method is probed with a full parser/checker pass.
Mitigation:
context-first candidate domains
partial-token prefix filters
expected-type filters
receiver/callee-specific lookup
probe budgets
truncated continuation metadata
cached parser/checker frontier data
budget exhaustion degrades to Incomplete
P5 incremental generator for deeper optimization
Dead can be unsound if the oracle does not have complete import, package, core, or receiver-method information.
Mitigation:
GenerateContext carries ProjectContext
SymbolCompleteness is explicit
Dead requires complete semantic domain
partial symbol information degrades to Incomplete
unknown-name Dead is forbidden under partial symbol context
A separate decode grammar can diverge from the real parser.
Mitigation:
generate grammar from parser/keyword source
snapshot generated grammar
freshness-guard tests
Tree-walker behavior can diverge from lowered Rust.
Mitigation:
parity harness
backend wins
unsupported constructs fail closed
The intrinsic boundary is the largest implementation risk.
Mitigation:
generate dispatcher from runtime_abi.rs and .rssi
freshness-guard tests
fixture-level parity
handwritten dispatcher only for tiny smoke tests
Sandbox behavior can differ from real host.
Mitigation:
label host mode in diagnostics
use fixed deterministic host for parity
perform final backend validation where needed
do not treat sandbox eval as final correctness proof
Agent-facing eval can encounter infinite loops, deep recursion, huge logs, or large values.
Mitigation:
step limits
wall-time limits
call-depth limits
heap/value limits
stdout/stderr/log limits
filesystem limits
fail-closed diagnostics
The interpreter tree-walker and lowered Rust backend may schedule async tasks differently. Exact log or side-effect ordering may be impossible to match for unconstrained concurrent programs.
Mitigation:
classify observations by ordering guarantee
use deterministic scheduler where strict parity is required
compare schedule-dependent events as multisets or canonical traces
exclude explicitly nondeterministic fixtures from strict parity
document async parity eligibility
Interpreter Map(IndexMap<...>) may preserve insertion order while the backend map implementation may not, or vice versa.
Mitigation:
specify RSScript map iteration order for interpreter-supported fixtures
ensure interpreter and backend use equivalent map ordering for supported parity fixtures
mark map-iteration fixtures unsupported until backend order matches
canonicalize map observations if a future spec makes order unspecified
RSScript should not rely on prompts alone.
AGENT.md gives the model the rules.
The generation oracle keeps it on valid paths.
The interpreter lets it test behavior quickly.
The checker and backend keep the language honest.
The review map keeps the product boundary explicit.
This is the RSScript-specific AI loop: generated source remains ordinary, explicit, and reviewable, while the compiler and runtime make invalid or untested drafts much cheaper to eliminate.
The design preserves the core RSScript model:
explicit source
checked contracts
fast sandbox probes
backend authority
review-first product boundary
The oracle and interpreter are not shortcuts around RSScript's discipline. They are mechanisms for making that discipline cheaper for agents to follow.