Skip to content

C++

language: "cpp" under the responses-as-code capability makes a model’s reply a C++ translation unit. The reply is written to disk verbatim as main.cpp and compiled by clang++ from wasi-sdk into a wasm32-wasip1 core module, then encoded in process with a pinned preview1 adapter into the WebAssembly component that turn is evaluated by. The bytes ride on the prepared program, so this arm declares no guest component of its own.

Nothing is prepended, appended or re-indented, so no line moves and a diagnostic at line 7 is line 7 of what the model wrote. A translation unit is the only C++ context admitting the template, namespace and #include declarations an author writes at a file’s top level. C++ has nowhere else to put a statement, so the reply must define main, which is the entry point gg’s shell calls.

A reply defining none is refused at prepare time as PrepareError::Unsupported, with a sentence naming what to write. gg has to ask that question because wasi-libc references main weakly, so a program with no entry point links cleanly and traps having run nothing. The reply is read lexically for the token main followed by an open parenthesis in its code bytes, so the reading can only be wrong in the accepting direction.

member_separator is ::, a code skill’s C++ file is a .hpp, and checker() is "clang++", so compile time is recorded on every turn.

Every name a program uses is reached through a line the program wrote, gg’s surface, the C++ standard library and a code module alike. gg is undeclared until the reply writes #include <gg/files.hpp> or the umbrella #include <gg.hpp>, and lib until it writes import lib.<key>;. The SDK’s headers are on the compile’s include path, a module’s precompiled interface is named to the compile, and the bodies of both are linked into every artifact, which is packaging rather than scope.

The compiler is a wasi-sdk tree, located at TCAB_GG_WASI_SDK_HOME, then /opt/gg/toolchains/wasi-sdk, then ~/.local/share/tcab/gg-wasi-sdk, and used by its real path, every symlink on the way resolved: clang derives its sysroot from where its binary actually is, so that is the path the compiler records and the one the arm rewrites to /wasi-sdk in everything it records, above all the libc++ header a hardening failure names. A compile is bounded at 120 seconds. crates/gg-sandbox-artifacts/cpp builds three artifacts from packages/gg-sandbox-cpp into that crate’s OUT_DIR, and the arm embeds them through GG_ARTIFACTS_CPP. None of them are committed. This arm’s warm-up is unpacking the guest archive.

ArtifactWhat it carries
cpp.guest.tar.gzthe generated WIT header, the SDK headers under the include/ root a program’s own #include <gg/…> resolves against, the library set prelude.hpp declares, gg’s shell as source and as an object, and the SDK, bindings and component-type objects the link needs
cpp.adapter.wasmthis arm’s pinned wasi_snapshot_preview1 reactor adapter
cpp.toolchain.jsonthe pinned release, target, standard and adapter version, and the header list read out of the prelude

Three compile flags decide what a model can write and read. -fwasm-exceptions with -mllvm -wasm-use-legacy-eh=false selects the standardised try_table encoding the pinned wasmtime accepts, so throw, try and catch work. -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE turns on libc++‘s bounds checks, which wasi-sdk ships disabled. -g1 supplies the debug information the engine symbolicates traps with.

The host accepts that encoding through Config::wasm_exceptions, which wasmtime gates behind its gc build feature. The workspace therefore builds against a wasmtime with GC support, and the repository’s other wasm hosts turn gc_support off on their own engines rather than inheriting a wider validation surface. gg’s engine is the one that opts in.

Sources/prelude.hpp declares the library set this arm makes available, which is the C++ standard library, header by header under // == Heading == groups. build.sh’s manifest, cpp.toolchain.json’s header list and the catalogue’s libraries section are all read from that one file. A program writes its own #include for every facility it names.

The SDK is hand-written in packages/gg-sandbox-cpp/Sources/sdk/ and reads as the standard library it arrives beside: snake_case functions and types, enum class for a fixed choice, aggregates for records, std::variant for a value that is one of two things, and a thrown gg::core::api_error deriving from std::runtime_error for a call that failed. One optional argument is a default argument; two or more are designated initialisers.

Every capability module is a namespace inside namespace gg, declared in one header of its own. A program writes #include <gg/files.hpp> and then gg::files::read_file(…), and #include <gg.hpp> is the umbrella declaring all thirteen. Each module’s catalogue entry states its own include line, which the system prompt’s module list and every documentation view of a symbol in it quote.

packages/gg-sandbox-cpp/signatures.sh reflects the signature catalogue out of clang’s own comment AST, dumped as JSON by the same clang++ that compiles every program and filtered with -ast-dump-filter=gg. crates/gg/build.rs runs it while building the crate. The reflection enforces four rules:

  • /// comments are model-facing and // comments are not. A public member documented with //, or with nothing, is left out of what a model is shown.
  • -Wdocumentation -Wdocumentation-pedantic -Werror=documentation is passed, so a \param naming an argument the function does not take fails the reflection.
  • The gg operation a function binds is written on its declaration as a <ggop>files.read_file</ggop> line, a namespace’s gg module as <ggmodule>files</ggmodule>, and a member function reaching an operation its module already offers as <ggop-alias>. A documented function that names no operation fails the reflection; an id gg’s operations table has no row for is caught the other way, by the registry gate over the catalogue this arm embedded.
  • The brief is the comment’s first line and the detail is the rest. A declaration whose first line runs on is refused by name.

A \throws names an error type a function’s own documentation declares, and the catalogue carries those types as that function’s throws list.

A code skill’s or memory’s namespace is lib::<key>. A program reaches it by writing import lib.<key>;, and then writes one of its names lib::<key>::<name>. That line is the model’s to write, on the same terms as the #include that reaches gg’s own surface.

Each module in scope is written into the compile workspace as a named C++ module — export module lib.<key>; over an export namespace lib::<key> opened around the author’s declarations where they stand — precompiled into a module interface of its own, and named to the program’s compile as -fmodule-file=lib.<key>=… and as a link input. Both arguments say where the module is and declare no name, so a program that writes no import for one earns use of undeclared identifier 'lib'. Each module is compiled on its own.

module and import are the two words a module-name component may not be and are both reachable keys, so each is escaped in the module name and left alone in the namespace. A key of module is reached by import lib.Module; and written lib::module::<name>.

The module declaration is what keeps gg’s surface out of the program. gg writes #include <gg.hpp> into the module’s global module fragment, whose names are attached to the global module and reach nobody who imports the module, so a program with a code module in scope reaches gg only through a line it wrote.

An author’s #include lines are hoisted into that same fragment beside gg’s own, above export module lib.<key>;. The fragment is where they belong for the reason gg’s line is there: its names are attached to the global module and reach no importer, so a module’s own headers stay the module’s own. #include is textual, so a line left inside export namespace lib::<key> would nest the header in that namespace.

Each hoisted line carries a #line stating where its author wrote it, and the #line 1 in front of the body is untouched, so a module’s body keeps the numbering its author wrote.

Each export of a module carries the type names its declaration writes in return position and in parameter position, read off that declaration, which is what the type views beside a function’s documentation view are opened from. A parameter’s own name, its default argument, and the const, reference and pointer decoration around a type are left out, and a template argument is a type name of its own, so std::vector<row> writes std::vector and row.

That precompile happens once, at the read that binds the module, so a module that does not build is reported to its author rather than to every program the agent writes afterwards. Its own file is the only authored source there, so a diagnostic located elsewhere is a toolchain failure. The interface and the file it was built from are kept in the loaded-module band of the agent’s compile workspace, and every later program is handed the interface rather than the source.

Everything clang++ rejects is one band, PrepareError::Compile, because clang has no parse-only phase a program passes before meaning is considered. A rejection is the model’s when a file somebody authored is named anywhere in the whole rendering, notes included, because a template error is reported inside the library with the model’s own line arriving as a note:. wasm-ld: error: undefined symbol: is also the model’s, and everything else is a toolchain failure. Warnings and the driver’s own linker command failed summary are dropped from what the model reads. See compilation for how the two kinds are reported.

What the model reads is capped group by group. The first four errors are kept whole, at most three note: lines are kept under one error, and what was dropped is counted rather than hidden. A diagnostic naming an authored file is never dropped at any depth, and clang’s own N errors generated. summary is kept. The band is decided on the whole rendering before the cap runs, so capping can never turn a compile error into a toolchain failure.

At run time there are four shapes. An uncaught throw is caught by gg’s shell and reported as a recoverable program error carrying the exception’s demangled class and its what(), with no location. A libc++ hardening check traps with libc++‘s own sentence at the model’s own line. A non-zero status returned from main is read by the shell and reported with the number the program chose, which is the one failure channel C++ gives an entry point. Any other undefined behaviour is a trap with no words, located at the model’s own line and no more.

Two failures this arm cannot report faithfully are recorded by gate G8. std::exit(3) reaches the model as an exit with a non-zero status, because the pinned preview1 adapter lowers every non-zero status to the same failure before gg is told, which the sandbox page states in full. A null dereference is not a fault at all: address zero is ordinary linear memory in wasm, so reading and writing through a null pointer succeeds and the turn is recorded as a clean one.

system-code.hbs reaches this arm through a segment gated on cpp, and code-nothing-shown.hbs through a clause naming printing. The segment states that the reply is compiled verbatim as a whole translation unit and defines int main.

Each module is a namespace inside namespace gg, and each entry of the module list beside the segment carries that module’s own #include line.

The arm names clang++ as its checker, so the shared body states that a program is compiled before it runs, that one the compiler refuses is not executed, and that a call the run withheld compiles and fails when it runs.

Source gg synthesizes for this arm is a whole program by the same rules. The file-view program, the documentation-view program and the bootstrap program each carry the include lines the calls they make need and a whole int main. A file view is written gg::views::open_file("src/main.cpp");, with a window as a designated initialiser.

crates/gg/src/sandbox/language/cpp.mask.rs is the arm’s one lexer over raw source: // and non-nesting /* */ comments, "…" and '…' with escapes, raw strings whose author-chosen delimiter is read rather than looked for, and the digit-separator rule — a ' straight after an alphanumeric is 1'000'000, not a character literal. The reader asking whether a reply defines main takes the scan’s best reading whatever happened, because its errors are safe in the accepting direction.