For the next Claude Code session working on this project. Read this first; it points at everything else.
Project location: C:\Users\paul\source\repos\Overt. GitHub: paulmooreparks/Overt (public, Apache-2.0).
An agent-first programming language — written, read, and maintained primarily by LLM agents, with humans in a review/audit role. Transpiles to readable source in host languages (C# primary via Roslyn, Go secondary). Sits alongside existing code via a "bridge, don't replace" deployment model rather than greenfield replacement.
The name is the design philosophy: every effect, error, dispatch, mutation, and piece of state is overt — visible at the call or declaration site, never concealed from the reader.
AGENTS.md— the operational doc for agents writing Overt. Every construct with one canonical example, every diagnostic code with its fix, the stdlib surface with real signatures, known gaps called out. Load this into context at session start whenever you'll be authoring.ovcode. NotDESIGN.md— that's rationale.README.md— status, layout, and a walk through the compiler pipeline. Fastest orientation for working on the compiler itself.DESIGN.md— authoritative design. 26 sections, ~1100 lines.
Fastest DESIGN.md orientation path (≈15 minutes):
- §1 Thesis — what Overt is and isn't
- §2 The central inversion (the table) — the core design instinct
- §4 Token economics and one canonical form — the primary evaluation lens
- §5 Semantic primitives — pointers to the three axes (concurrency, errors, iteration) + mutation + traces
- §7 Surface syntax — what code actually looks like
- §17 FFI and §19 Module system and packaging — how Overt integrates with existing ecosystems
- §18 Backend strategy — includes the debug-mapping subsection that makes runtime errors resolve to
.ovsource
Five-minute version: §1, §2, §5.
Grammar specs (authoritative for the parser/lexer):
docs/grammar/lexical.md— token grammar including the string-interpolation mode automaton.docs/grammar/precedence.md— operator precedence and associativity.
The frontend works end-to-end on C# with real semantic enforcement. Every example in examples/ transpiles into C# that compiles cleanly via Roslyn, pinned by a test for every example. The transpiled examples/hello.ov actually runs and prints "Hello, LLM!" — verified by the tests/Overt.EndToEnd harness.
What exists today, pinned by 359 passing tests:
- Lexer (mode-stack, full interpolation, token-stream goldens for every example).
- Parser (recursive descent, full precedence grammar, all 12 examples parse cleanly).
- Name resolver (symbol table, no-shadowing, ambient prelude scope, did-you-mean suggestions, module-qualified stdlib resolution for
List.empty/Trace.subscribe/CString.from). - Type checker with full semantic enforcement — 11 diagnostic codes that reject real bugs:
- OV0300..0306: type / arity / field / arm / condition mismatches
- OV0307: ignored
Result(DESIGN.md §11 guarantee) - OV0308: non-exhaustive match on user enums,
Option, andResult(DESIGN.md §8's "single most valuable check") - OV0310: uncovered effect rows — direct calls, module-qualified calls, and higher-order propagation through effect-variable argument inference
- OV0311: refinement-predicate decidability at literal boundary crossings (DESIGN.md §8)
- OV0312:
break/continueoutside a loop body - OV0313:
for eachiterable must be aList<T> - Non-generic type aliases are transparent for type-equality; the refinement check is the only layer that fires on
let a: Age = 42.
- Synthetic stdlib declarations with real signatures —
Result,Option,List,Ok/Err/Some/None,println, collection operations,Trace,CString, variant lists forOption/Result. - Real stdlib runtime implementations (
Overt.Runtime.Prelude) —map,filter,foldoverImmutableArray;par_mapruns concurrently viaParallel.Forand returns first-Err by original index;Trace.subscribe/Trace.emitdispatch to registered subscribers. Transpiled Overt programs that touch collections now run, not just compile — verified byStdlibTranspiledEndToEndTests, which compiles a small.ovprogram in-memory and invokesModule.main(). - Int64 primitive type. Overt now has
Int(32-bit, lowers to C#int) andInt64(64-bit, lowers to C#long) as distinct primitives. Previouslylong-returning BCL methods were// skipped; now they're part of the binding surface. UnlocksEnvironment.TickCount64,TimeSpan.Ticks*,DateTime.Ticks, and all fixed-constant fields typed aslong(e.g.,TimeSpan.TicksPerSecond). BindGenerator mapstypeof(long) -> Int64directly. Overloads that differ by int-vs-long (e.g., Math overloads) stay distinguishable via the type-suffix machinery. - Value-type (struct) extern support. Structs like
DateTime,TimeSpan,Guidnow emitextern typeand their full instance/static surface. DateTime went from 2 to 51 externs; TimeSpan from ~9 to 58. Instance properties emit as zero-arg-besides-self externs; the emitter's::handler detects properties via reflection and emits bare member access (self.Year) instead of a method call. - Cross-type opaque extern references.
overt bind --with-opaque <FullName>[=<module>]lets a generated facade reference OTHER opaque types the user has declared elsewhere. The generator renders the type under its Overt short name, and (if a module path was provided) emits ause <module>.{<Name>}at the top of the facade.StreamReader(Stream)andHttpClientmethods takingUriboth work now. Registry is repeatable at the CLI. - CLI force-loads common BCL assemblies.
System.Net.Http,System.Text.Json,System.Text.RegularExpressions, etc. aren't loaded by default in an AppDomain that only touches our own code;overt bindnow explicitly touches them so reflection can reach them. - Tier-1 audit passed (Go-back-end corner check). Confirmed zero hardcoded
"csharp"strings inOvert.Compiler;ExternDecl.PlatformandExternDecl.BindsTargetare opaque data handled only inOvert.Backend.CSharp.Overt.Compiler.csprojhas zero project/package references.Stdlib.csdefines language-level prelude names (Result/Option/List/map/filter/par_map/println) — per-back-end runtimes must each provide them but the names are shared. - Opaque-type extern bindings: instance methods and constructors.
extern "csharp" type StringBuilder binds "System.Text.StringBuilder"declares an opaque Overt type whose host representation is a full type name. The C# emitter turns this into ausing StringBuilder = global::System.Text.StringBuilder;type alias at the top of the generated file. Extern functions binding toNs.Type..ctoremitnew global::Ns.Type(args); extern functions binding toNs.Type::Methodrequire a first param namedself: Tand emitself.Method(args). BindGenerator auto-emits all three shapes (static members + constructors + instance methods) for reference types, soovert bind --type System.Text.StringBuilderproduces a usable facade in one command. Aliased module imports (use foo as bar) bring the imported module's types into scope unqualified alongside the alias, matching C#/Rust's using-alias ergonomics. - Property + field access in externs, type-based overload disambiguation. The C# emitter now consults reflection at emit time to decide whether a binds target is a property (bare member access) or method (parenthesized call).
System.Environment.MachineNameemits asSystem.Environment.MachineNamewith no parens;System.Environment.Exit(code)keeps its call shape. BindGenerator emits public static properties and fields as zero-arg externs alongside methods; overloaded methods disambiguate by C# parameter type names soMath.Abs(int)becomesabs_intandMath.Abs(double)becomesabs_double(both would have beenabs_1under arity-only). Parameters that would shadow top-level facade names (e.g.,Environment.Exit(exitCode)+Environment.ExitCode) get an_argsuffix so Overt's no-shadow rule doesn't reject the generated facade. - Blessed stdlib with auto-discovery, per-back-end structure. Facades live under
stdlib/<backend>/*. Today onlystdlib/csharp/system/*exists (the only back end emitting code):stdlib.csharp.system.io.path,io.file,math,environment,guid,convert,console. The CLI'sDiscoverSearchDirswalks up fromovert.exelooking for any ancestor containingstdlib/, souse stdlib.csharp.system.io.path as pathjust works.$OVERT_STDLIBoverride accepted.install.ps1copiesstdlib/alongside the published binary. Generated facades useglobal::<binds-target>in emitted calls so they don't collide with Overt's ownSystem.*namespace underOvert.Generated.Stdlib.Csharp.System.*. - Cross-file modules via
use— both shapes. Two import forms now work end-to-end:use a.b.{sym1, sym2}(selective, symbols in scope unqualified) anduse a.b as alias(aliased, access viaalias.sym). Dotted paths walk directories:use stdlib.http.clientresolves tostdlib/http/client.ovin the search-path directories (entry file's dir by default). ModuleGraph discovers sibling and nested.ovfiles, topologically orders imports, detects cycles. NameResolver threads exports through; TypeChecker sees their types viaimportedSymbolTypes. Emitter emitsusing static Overt.Generated.<Path>.Module;for selective andusing Alias = Overt.Generated.<Path>.Module;for aliased.overt run main.ovhandles the full graph; other emit modes are still single-file. Wildcard imports forbidden (DESIGN.md §19) — OV0163 if you try. - C# extern runtime + facade generator — the BCL is reachable.
extern "csharp" fndeclarations now lower to real calls into the bound C# method. AResult<_, IoError>return automatically wraps the call in a try/catch that converts exceptions toErr(IoError { narrative }).overt bind --type System.IO.Pathreflects on a .NET type and emits an Overt facade with effect rows inferred from a curated namespace table (pure forMath/String/IO.Path;io,failsfor most I/O;io,async,failsforNet.*). Overloads are disambiguated by arity suffix (combine_2,combine_3). Parameters/returns the generator can't map cleanly emit as// skippedcomments. An Overt program can now callSystem.IO.Path.Combine,System.IO.File.ReadAllText, etc. end-to-end, verified byTranspiled_ExternCsharp_*tests. - Formatter.
overt fmt <file>emits one canonical form: four-space indent, trailing commas on multi-line lists, one statement per line, match arms one per line, named-arg calls for multi-arg.--writeupdates in place. Comment-preserving — line comments survive a round-trip. Backed by 13 idempotence tests (every example formats to a fixed point and the result re-parses cleanly). Prerequisite: the lexer now emitsLineCommenttokens; the parser filters them out of its token stream on entry; the formatter reads the unfiltered stream to re-interleave comments at their source positions. - Imperative control flow:
for each,loop,break,continue, plus literal patterns inmatch. The parser, checker, and emitter all handle them end-to-end.for each x in xs { ... }lowers to C#foreach (var x in xs.Items),loop { }towhile (true),break/continueto their C# equivalents. OV0312 rejectsbreak/continueoutside a loop body; OV0313 requiresfor each's iterable to beList<T>. Literal patterns (0,1,-1,true,"exit") match the scrutinee for equality and don't contribute to exhaustiveness — a match using them still needs_. - Faithful
?/|>?propagation — errors are values, not exceptions. The emitter's?-hoisting pass transforms every always-evaluated?and|>?site into avar __q_N = ...; if (!__q_N.IsOk) return Err<E>(__q_N.UnwrapErr()); var __qv_N = __q_N.Unwrap();preamble before the enclosing statement, then substitutes the unwrapped local at the original site. Conditionally-evaluated sites (inside if/match/while arms or block expressions) fall back to.Unwrap()and are marked as a follow-up. Verified by new end-to-end tests that catch an Err flowing out ofModule.main()as a returned value. - C# emitter (type-aware, expected-type threading for generic inference, stdlib variant-pattern lowering,
#linedirectives for PDB mapping back to.ovsource). - Runtime prelude (
Overt.Runtime) —Unit,Result<T, E>,Option<T>,IoError,RaceAllFailed<E>,List<T>and friends, target-typed marker structs forOk/Err/Some. - End-to-end harness that regenerates
Generated.csfromhello.ovon demand (OVERT_REGEN_HARNESS=1 dotnet test) and runs the transpiled program. - Debug mapping via portable PDB (§18). Runtime errors, debuggers, and stack traces resolve to
.ovsource, not.cs. - Compiler Explorer release engineering staged —
.github/workflows/release.ymlbuilds a Linux x64 self-contained binary on anyv*tag; the two-PR submission playbook is intooling/godbolt/SUBMISSION.md. Waiting on an explicit tag push.
What's notably absent, ordered by impact on "can I write real code in this":
?deep inside a call argument within an if/match arm. Directlet x = if cond { foo()? } else { bar }now lowers to stmt-level C# if/else with proper early-return. Butfoo(if cond { bar()? } else { baz })— the?is buried inside an argument inside an arm — may still fall back to.Unwrap()-that-throws. Workaround: lift the?to a preceding let.- Block comments.
// lineworks;/* ... */block comments don't parse. Unblock trivial, low priority. - Runtime-assertion emission for undecidable refinement predicates. The checker marks
size(self) > 0-style predicates as "needs runtime check"; the emitter does not yet generate the check. Works around it today by writing the validation in user code, as refinement.ov does. - Formatter. Not started. Canonical form is enforced by convention in the examples, not mechanically. Blocks the
@review/@agentcomment-tooling story. - Tuple-of-enum exhaustiveness.
match (state, event) { ... }skips the check today; state_machine.ov works because it has a wildcard catch-all. - Go back end. Scaffold only; no emission.
- Packaging. NuGet packaging story queued (see "MSBuild integration" below —
dotnet packproduces nupkgs, public push to nuget.org is the remaining task). The module system itself ships ~now; see "What the next session could reasonably start on" below for the per-feature status. MSBuild integrationResolved 2026-04-24.src/Overt.Build/ships anOvertTranspileTask+Overt.Build.targetsthat wires.ovfiles into a C# project's compile pass. Importing the targets auto-includes every.ovunder the project, transpiles toobj/.../overt/<Name>.g.csbefore Csc, and feeds the generated paths into@(Compile). Diagnostics route throughIBuildEngine.LogError/LogWarningso OV codes surface in the IDE's error list. Consumed via<PackageReference Include="Overt.Build" Version="0.1.0" />: the nupkg lays outbuild/Overt.Build.targets(auto-imported by NuGet),tasks/net9.0/*.dll(the task assemblies), andlib/net9.0/Overt.Runtime.dll(flows into the consumer's Csc reference list). Dev-in-repo form lives atsamples/msbuild-smoke/; the package-consumption path is exercised byOvertBuildNuGetTests.Packed_Nupkg_IsConsumableViaPackageReference. Publishing to nuget.org is the remaining task — the nupkg is produced on everydotnet packbut not yet pushed to a public feed.- LSP / IDE integration. Diagnostics available via CLI only. No hover, go-to-definition, etc.
Ordered by "how directly this unblocks someone writing real Overt code":
-
Closures / anonymous fns (multi-session).
filter(xs, x => predicate_using_outer(x))is the natural shape and Overt has no equivalent. Bothsamples/logtally/andsamples/diffconf/ended up withlet mut+for eachloops + free helper fns where a captured-state predicate would have read in one line. Capture-by-value is the natural model (matches the immutability story; sidesteps Rust-style borrow lifetimes); effect rows on closures inherit from the body and propagate through the receiving fn parameter (fn(T) !{io} -> U). Stdlib already accepts fn-typed parameters, so no signature-level grammar change needed — work is parser (anonymous-fn syntax), type-checker (capture analysis + effect propagation), and emitter routing (C# lambdas / Go closures, both first-class). -
Set.values<T>(set: Set<T>) -> List<T>(½ session). Set has full create / contains / insert / remove / set-algebra ops but no way to iterate the result.samples/diffconf/dropped the naturalSet.differencepath because of this. Symmetric to the existingMap.values; trivial on both back ends. Seedocs/osl.mdCandidates.Named multi-returnShipped. Cribbed from Sutter's Cpp2. A fn can declare multiple named return values without a top-level record decl per ad-hoc shape:fn list_diff(left: List<String>, right: List<String>) -> (added: List<String>, removed: List<String>) { ... }New AST nodes (
NamedTupleType,NamedTupleExpr), TypeRef variant (NamedTupleTypeRef), parser cases (lookaheadIdentifier :for type position;Identifier =for expr position), type-checker handling, formatter, and both back-end emitters (C# value tuples; Go anonymous structs).samples/diffconfrewritten —list_added+list_removedcollapsed into onelist_diffreturning the named tuple. -
C# emitter: IIFE-wrapping context lossResolved. All five quirks either fixed or obviated:- Side-effect-only
if/matchinside a for-each loop body now emits as a real if / switch statement instead of an IIFE-wrapped ternary. Fix: route block trailing expressions through the statement-position emit path (EmitExpressionAsStatement), with a Unit-literal special case that emits an empty statement. Resultpatterns (Ok,Err) inside an IIFE-wrapped match arm body — and any other pattern-bound name inSome(x)/Ok(x)/Err(x)— now bind to the right inner type. Fix: pattern-binding type inference (BindPatternSymbols) extracts inner types fromOption<T>/Result<T, E>for the constructor-pattern arg slot.?inside a for-each inside an IIFE-wrapped match arm body now emits the proper early-return shape (if (!__q.IsOk) return Err<E>(__q.UnwrapErr());) within the lambda. Verified by inliningsamples/diffconf'srender_list_changedback into the calling match arm — works correctly. Fixed transitively by the pattern-binding work above (the IIFE's return type was previously?-typed, defeating the?-hoist's type check; now it's correctly typed).- Generic record literal (
Pair { left = ..., right = ... }) lacks type-arg inference. Obviated by named multi-return — the canonical replacement for ad-hoc pair shapes is(left: T, right: U)named tuples, which type correctly. Pair remains in the stdlib for List.zip / List.unzip's return type but user-side construction isn't expected. - User-defined generic records (
record Box<T> { ... }) aren't supported — RecordDecl has no TypeParameters. Separate language feature, not part of the IIFE cluster.
- Side-effect-only
-
Generic-method opaque support (low priority).
HttpClient.GetFromJsonAsync<T>(Uri)and similar BCL methods that take type parameters are skipped by BindGenerator's auto-facade. The underlying capability is present — hand-binding viabinds "Type.Method<YourType>"already works (seeexamples/csharp/json.ov,samples/diffconf,samples/valconf). Auto-gen would save ~3 lines of extern per concrete instantiation. Not currently biting any sample; revisit when one needs N instantiations of the same generic method. Sized: ~1 session for opt-in via--instantiate-withflag; ~2 sessions for per-call-site synthesis.Void-Task externsShipped. Extern declared-> Task<()>now wraps withasync Task<Unit>+await callExpr; return Unit.Value;. Sister fix shipped at the same time:-> Task<Result<T, E>>extern wraps withasync Task<Result<T, E>>+try { await callExpr; return Ok(...); } catch { return Err(...); }— converts throwing async host methods into Result-shaped Overt calls. Both verified bysamples/portcheck(bindsTcpClient.ConnectAsyncend-to-end with Result-shaped probe).par_map_asyncShipped. Stdlibpar_map_asyncregistered with callback typefn(T) !{io, async, E} -> Task<Result<U, E>>and return typeTask<Result<List<U>, E>>— both sides explicit-Task becausepar_map_asyncis a stdlib-registered fn (not a user fn), so the type-checker's implicit-async-wrap pass doesn't apply. The user-side closure body just calls an async fn without.await, surfacing the host-side Task as a value the runtime hands toTask.WhenAll. Caller doespar_map_async(...).await?.samples/portcheckrewritten to use it (probe now returnsResult-wrapped); confirmed parallel viaParMapAsync_OverlapsCallbackTasks(8 × 100ms delays complete in <400ms wall). Surfaced no new emitter bugs — closures whose declared return isTask<...>already emit asFunc<T, Task<R>>correctly because the host call-site Task is just a value flowing through a sync lambda. -
Task/async interop at the extern boundaryResolved 2026-04-24. Overt gainedTask<T>as a named type (maps toSystem.Threading.Tasks.Task<T>) and a postfix.awaitoperator that mirrors?. Externs bind toTask<T>-returning BCL methods by spelling the return type directly. User fns whose body uses.awaitemit as C#async Task<ReturnType>; callers seeTask<T>and unwrap with.await. Fns carryingasyncin their effect row for other reasons (par_map,parallel,race) keep their sync call-site shape — the compiler keys off.awaitpresence, not the effect row alone. Diagnostics: OV0317 for.awaiton a non-Task, OV0310 for missingasynceffect. Example:examples/json.ovshows the pattern's sibling (generic method via angle-bracket binds target) without awaiting; see the testTranspiled_Async_AwaitOnTaskReturningExtern_RoundTripsfor the full roundtrip. AST-walker drift guards (½ session — exploration). Three Class-A bugs surfaced this session: NamedTupleType missing from formatter (rendered/* ? type */), AwaitExpr missing from formatter (/* ? AwaitExpr */), AwaitExpr missing fromEmitExpressionAsStatement. One Class-B bug:BodyContainsAwaitExprdidn't descend into for-each / while / loop, so async fns with awaits inside loops emitted as non-async. Two interventions, ranked:(1) ReplaceShipped. Both sites in/* ? ... */formatter defaults with throws.Formatter.cs(types and expressions) nowthrow InvalidOperationExceptionnaming the unhandled AST variant. 517/517 still green, so the throws don't fire on any current AST — confirms the existing cases are complete. Going forward, adding a new TypeExpr or Expression variant without a Formatter case fails the first test that round-trips it, instead of silently emitting/* ? Foo */.(2) Visitor base class for fold-style walkers. Three of them (
BodyContainsAwaitExpr,ContainsPropagate,ContainsReturn), each ~25 cases of mostly-identical descent logic. Migrate toabstract class ExpressionFold<T> { abstract T Default(); abstract T Combine(T, T); virtual T VisitAwait(AwaitExpr) => DescendChildren(...); ... }à la Roslyn'sCSharpSyntaxVisitor<TResult>. Subclasses override only the cases they care about; default child-descent is provided once. New AST node → one new virtual on the base → all three walkers pick up correct descent for free. Cost: ~half session. Catches Class-B bugs (the loop-descent miss). Skip the formatter and emitter switches — those are 144+45 cases of per-case-unique logic, splitting them into per-method visitor fragments doesn't catch more bugs and adds significant boilerplate. Recommendation: defer until either a fourth walker is needed or another Class-B bug surfaces. The current line count is 3 walkers × ~25 lines = ~75 lines; visitor base + 3 stubs is ~150 lines total — net more code for the same expressivity.Net: ship (1) as a one-line guardrail; queue (2) as conditional-on-pain. Both are mechanical when triggered.
-
Extern grammar extensions (1 session). Today extern handles static methods. For BCL we also need: instance methods (with a
selfparameter convention), constructors (binds to..ctor), properties (zero-arg binds to a static or instance property getter). The binding runtime already handles static-method-shaped bindings — the changes are in the parser and emitter. -
MSBuild integration— shipped; see the "not yet implemented" list's resolution entry. -
Conditional-contextResolved 2026-04-24. The emitter now pre-lifts any?— remaining deep-nesting case.if/matchsubtree that contains?and is nested inside another expression (call arg, record field, tuple element, etc.) into a local via the same stmt-lowering shape, then substitutes the local at the original site.foo(if cond { bar()? } else { baz })propagates as a value; two-levels-deep (if outer { consume(if inner { foo()? } else { … }) } else { … }) also works — each lowered branch body re-runs the lift pass, so inner conditionals get their own pre-lift before emission. -
Runtime-assertion emission for undecidable refinement predicates (1 session). The emitter's implicit-operator generator on wrapper records should evaluate the predicate and throw on violation. Closes the last gap in the refinement-types guarantee.
-
Diagnostic upgrade:
note:pointers intoAGENTS.md(½ session).AGENTS.mdexists now. Next is plumbing diagnostics to includenote: see AGENTS.md §<N>so an agent hitting an OV code learns the rule from the error message, in context, without needing to go look. Touches every diagnostic site in the compiler but each touch is one line. -
Formatter (2 sessions). Rules are in §21. Rust's
rustfmt/ Go'sgofmtis the shape — consumes AST + trivia, emits canonical source. Needed for@review/@agentcomment tooling and for asserting "one canonical form" mechanically. -
Tuple-of-enum exhaustiveness (1 session). Cartesian-product walk over arm patterns in
match (a, b)against enum types. Additive to OV0308. -
Go back end (2–3 sessions). Fresh emitter against the same AST + TypeCheckResult. Real forcing function for the IR being runtime-neutral per §20. Once a single example emits identically through both, the conformance suite is real.
-
MSBuild integration— shipped 2026-04-24; NuGet packaging is the remaining follow-up. -
LSP server (multi-session). Reuse the parser and type checker; wire diagnostics to publishDiagnostics, implement hover / go-to-definition via ResolutionResult and TypeCheckResult. Blocks good IDE integration.
-
Module systemShipped 2026-04-28. Cross-file imports were already wired throughModuleGraph+ the topological per-module resolve/type-check loop inCli.CompileGraphfrom earlier work; the remaining gaps closed this session were (a) the C# emitter's import side now emitsusing {ns};alongsideusing static {ns}.Module;so cross-module record/enum types resolve at the host level, (b)pubparses on every top-level decl kind andCli.CompileGraph::CollectTopLevelExportsfilters toIsPub: trueonly — module-private by default per DESIGN.md §19, (c) OV0168's diagnostic note now points the user atpubwhen an importer names a private symbol. Cross-module records, enums, and fns covered byTranspiled_MultiModule_*end-to-end tests;Graph_PrivateSymbolImportReportsOV0168covers the visibility boundary. Known limitation: non-generic refinement aliases (type Foo = Int where ...) lower to file-scoped C#usingaliases, so they don't survive cross-module import yet — a wrapper-record lowering would close it. Generic refinements already lower to wrapper records and travel correctly. A package-discovery story (NuGet-shipped.ovmodules, version resolution) is separate from the module system itself and lives under "Packaging" above. -
Full type inference with unification (multi-session). Solve generic type arguments at call sites; propagate through argument-driven inference. Would eliminate several emitter workarounds.
These are observations from agent use of Overt that aren't yet validated at scale but are concrete enough to capture. They're about which design decisions make reading/writing/reasoning easier for LLM agents — the primary audience.
Observation. Same computation expressed three ways in examples/app.ov-style programs:
// Three lets — verbose, zero implicit transformations per line.
let b1: StringBuilder = sb.new_()
let b2: StringBuilder = sb.append_string(self = b1, value = "hello ")?
let b3: StringBuilder = sb.append_string(self = b2, value = "from Overt")?
let result: String = sb.to_string(b3)?
// let mut — single accumulator, explicit mutation.
let mut b: StringBuilder = sb.new_()
b = sb.append_string(self = b, value = "hello ")?
b = sb.append_string(self = b, value = "from Overt")?
let result: String = sb.to_string(b)?
// Pipe chain — terse, two implicit transformations per arrow.
let result: String =
sb.new_()
|>? sb.append_string(value = "hello ")
|>? sb.append_string(value = "from Overt")
|>? sb.to_string
The agent self-report (Claude Opus 4.7, 2026-04-24): the three-let form
is easiest to RWRA. No implicit semantics anywhere; every line tells its
inputs and outputs directly; edits are local. Pipes cost two implicit
operations per arrow (positional splice + |>? unwrap) that the agent must
simulate mentally at each step. let mut sits in the middle — the mutation
tracking is a smaller tax than pipe mechanics but bigger than no tax at all.
Why this matters. Pipe syntax is optimized for human visual-flow recognition. The three-let form is optimized for agent step-by-step reasoning. Overt's target is agent RWRA primary, human RWRA secondary — which suggests pipes are a human-optimized feature that may not earn their seat at the table.
Hypothesis to validate. As real agent-authored Overt programs accumulate, measure: when agents are free to pick between equivalent pipe and three-let forms, which do they pick, and which yields fewer bugs on modification? If the finding holds:
- AGENTS.md should steer agents toward the three-let (or
let mut) form as canonical, and explicitly mark pipes as an expert idiom. - The formatter could consider de-pipelining when it can prove equivalence.
- Pipes might get reclassified from "core" to "v1 optional" — if the empirical data says they hurt more than they help, removing them from the language is on the table.
Do not pre-emptively strip pipes; the finding is a single-agent self-report, not data yet. Validate first.
? in a direct if-expression arm now lowers to early-return (stmt-level
restructuring). ? buried deep inside a call argument within an arm falls
back to .Unwrap()-that-throws — same ? character, different semantics,
no visible marker. This is exactly the "implicit transformation per site"
problem that pipes have, except worse because the inconsistency is hidden.
Treat the fix (extend NeedsStmtLowering into call args) as higher
priority than its "½ session, low urgency" estimate suggests. An agent
cannot safely rely on ? unless its behavior is uniform.
A pass over the language looking for implicit-per-line decisions an agent must carry. Full analysis is in the session transcript; the short list:
- H3. Type alias transparency vs nominality. Non-generic aliases are
transparent (
Age == Int); generic aliases are nominal. Same keyword, context-dependent semantics. Needs validation before changing. - H4. Extern binds-target punctuation encoded three call shapes in an
opaque string (
./::/..ctor). Resolved 2026-04-23. First-classextern "csharp" instance fn/extern "csharp" ctor fnkeywords now select shape; binds target is always a dotted path. Diagnostics OV0315 (missingself) and OV0316 (missing return type on ctor) catch misuse. All stdlib facades regenerated. - H5. Optional type annotation on
let. Resolved 2026-04-23. Required via OV0314; all examples and tests updated. - H6. Optional trailing
;after statements. Resolved 2026-04-23. Stray;now rejected with OV0170; newlines separate statements. - H7.
|>?vs|>— same prefix, different semantics. Moot if H1 reclassifies pipes as expert idiom; no standalone action. - H8. Refinement-predicate silent deferral when undecidable.
Resolved 2026-04-24. Generic refinements throw
RefinementViolationat the wrapper's implicit operator. Non-generic refinements route through synthesized__Refinements.{Alias}__Checkhelpers that the emitter wraps around boundary expressions — call args, let initializers, record field inits, and function return expressions. Statically-proven-safe literal crossings skip the wrap; undecidable and non-literal crossings always run the predicate. - H9. Block-as-expression trailing value is implicit per-block. The Rust/ML tradition thinks this earns its keep; hold for validation.
Strengths confirmed in the same pass (keep as-is): named args on
multi-arg calls; explicit self for instance methods; effect rows on every
fn; exhaustive match; no shadowing; no method-call syntax; errors as Result
values; single-arg positional as ambiguity-free zone.
What the three "actionable now" items share: none of them required empirical agent data because the cost is measurable at the language-design level — each eliminates an implicit decision rule the agent must carry. All three (H4, H5, H6) shipped together 2026-04-23.
Three surfaces, each doing a different job. All three matter; none substitutes for the others.
AGENTS.mdat repo root — the grounding document. ~400–600 lines, terse, example-driven, one canonical form per construct. Loaded verbatim into agent context at session start. Covers: module shape, effect rows,Result/?,matchexhaustiveness, refinement types, record updates withwith, pipe composition, FFI boundaries, stdlib surface with real signatures, what eachOV0xxxdiagnostic means and the canonical fix. NotDESIGN.md— that's ~1100 lines of rationale; wrong artifact for a working agent.examples/as a reference corpus. Already plays this role. Agents grep for idioms (parallel,with,FFI) and read the example. Living test cases — if they stop compiling, CI catches it. Rule: if an agent can't find a pattern inexamples/, the language doesn't really have it yet. As new stdlib/control flow lands, add examples that exercise them.- Compiler diagnostics as in-situ docs. An agent hitting
OV0310learns the effect-row rule from the diagnostic itself, in context, at the exact moment it's relevant. Every diagnostic should be self-contained — the primary message, ahelp:with the canonical fix, anote:pointing at the relevantAGENTS.mdsection. This is the pedagogically strongest position a doc can occupy and costs almost nothing beyond what we already emit.
The LSP (when it lands) adds hover-for-type and hover-for-effect as a fourth surface, but it's additive, not load-bearing.
These are settled by explicit discussion with recorded rationale in DESIGN.md. Re-opening them burns tokens and drifts the design. If a new requirement genuinely breaks one, surface it as new data — don't hand-wave it open.
- Non-generic type aliases are transparent for type-equality.
type Age = Int where ...meansAgeandIntcompare equal structurally; only the refinement predicate distinguishes them. Generic aliases (type NonEmpty<T> = List<T>) stay nominal — they emit as wrapper records. (Locked 2026-04-23.) - Language name: Overt. Extension:
.ov. Registry conflicts were checked. - Surface syntax: C-family with ML semantics (§6). Not indentation-significant.
- Type system: Static, non-nullable by default, no reflection, no user-defined macros, no full dependent types, no lifetime annotations, no subtyping, invariant generics (§8, §15).
- No literal integer indexing at source level (§13). Zero-cost iteration is the workaround; proven-index is the numeric-kernel escape hatch.
- No method-call syntax. Pipes (
|>and|>?) for composition; bare calls otherwise (§7). Dots mean record field access or module-qualified call, nothing else. - Errors are values, never exceptions.
Result<T, E>with?propagation (§11). Exceptions convert at FFI boundaries, not in source. - No undefined behavior in safe Overt. Every UB source from C/C++ is designed out structurally (§8 "Defined behavior"). Integer overflow traps by default; wrap / saturate / checked are opt-in stdlib functions. Release behavior equals debug behavior.
- No shadowing across nested scopes (§3). Every name has one binding. Prelude names are an exception: patterns and locals may reuse them (otherwise
match opt { Some(v) => v, None => 0 }would conflict with stdlibNone). - Else is optional on
ifin statement position.if cond { body }is sugar forif cond { body } else { () }; the then block must have type()(§4, resolved 2026-04-22). - Comparison and equality are non-associative.
0 <= x <= 100is a parse error; use0 <= x && x <= 100(resolved 2026-04-22;docs/grammar/precedence.md§4). - Effect rows are explicit on every function, row-polymorphic via effect-row type variables. Core effects:
io,async,inference,fails(§7). - Immutable records.
let mutfor rebinding local names;withfor modified copies (§10). No shared mutable state, no mutable references. - Transpile to source, not IR. Do not reach for LLVM without a concrete target the current back ends cannot reach (§18).
- Debug mapping via
#linedirectives and portable PDB (§18 debug-mapping subsection). No Overt-specific debug format. Generated.csfiles are read-only by construction; runtime errors resolve to.ovsource. - Comment tags:
@review:(agent → human, resolved by deletion) and@agent:(human → agent, persistent). No threading, no status flags, no taxonomy (§21). - One canonical form enforced by formatter. Rules in §21; no per-project or per-developer configuration.
- Host language for the compiler: C# on .NET 9 (§20).
- Per-back-end stdlib is primary; portability is a separate back end, not a language feature (§19).
stdlib/csharp/*binds to .NET idioms; futurestdlib/go/*would bind to Go idioms. No platform-neutral abstraction layer in the core. Programs that need portability use a future portable back end with its own stdlib engineered for that purpose — users opt in explicitly. Rationale: agent-driven retargeting is cheap; portable abstractions are expensive forever. (Locked 2026-04.) - Tooling is tiered: some pieces are shared across all back ends, and some are per-back-end (§20). Lex / parse / resolve / type-check / format / module graph / OV diagnostics / LSP protocol live in
Overt.Compiler(one implementation, shared). Emission, runtime library, binding generator (overt bind), runner (overt run), debug mapping, host-source inspection, and package-system interop live in eachOvert.Backend.<Host>project. The CLI is a thin dispatcher.BindGeneratormoved fromOvert.CliintoOvert.Backend.CSharpto make the split real. (Locked 2026-04.) - License: Apache-2.0.
The author (paul@smartsam.com) has 26 years of C# experience and concurrent Go experience. This informs the back-end choices and the expectation that C# interop should feel native. Communication style is terse and direct — short answers, concrete proposals, tradeoff analysis over abstract philosophy.
DESIGN.mdis authoritative. Capture decisions there; do not let them live only in chat.- When adding new sections, renumber carefully — cross-references are scattered throughout the doc.
grep "^## \d" DESIGN.mdshows the current structure. DESIGN.md §5 Semantic primitivesis the navigation hub — it points at the key detailed sections.- Examples in
/examples/are test cases for the design. If committed syntax can't comfortably express a real pattern, that is a signal to revise the design, not the example. - Keep examples honest: use the same pipe syntax, no method calls, no positional args beyond named-elision where the single-arg rule applies.
- The
.csfiles the emitter produces are not source. They live undertests/Overt.EndToEnd/Generated.csand the test suite regenerates them onOVERT_REGEN_HARNESS=1. Never edit generated C# directly — fix the.ovsource or the emitter instead. Runtime errors already point at.ovlines via portable PDB, so this discipline holds naturally.
The language is usable: 13 diagnostic codes reject real bugs; real stdlib runtime; faithful ? propagation (including inside if/match arms); full imperative control flow; literal match patterns; canonical overt fmt; every diagnostic points at the relevant AGENTS.md section; and — new this session — extern "csharp" actually calls the BCL, with overt bind generating typed, effect-annotated facades via reflection. 341/341 tests. Godbolt release engineering staged and waiting on a tag push. The architectural tiers still outstanding: Go back end, module system (so facades can be use-imported across files), LSP, MSBuild integration (for .csproj + NuGet).