docs/agents/agent_standards.md
WAP/WML Browser Emulator Agent Standards (v3)
Project Codename: Waves
Owner: Dylan Steele
Target Platform: Unix / macOS tooling + Tauri host
Rendering Engine: Rust (native + WebAssembly targets)
Transport Layer: Rust (transport-rust/)
Adapters / Host UI: TypeScript (browser/frontend)
Purpose
This document defines operational standards and architectural contracts for contributors and agents working across the Waves WAP emulator stack.
Primary stack layers:
- Rust runtime engine (
engine-wasm/engine) - Rust transport library (
transport-rust) - TypeScript/Tauri host adapters (
browser)
Canonical Layer Responsibilities
transport-rust/- network/protocol concerns (HTTP/WAP paths, gateway adaptation)
- WSP/WBXML handling and payload normalization
- deterministic transport error taxonomy and request correlation metadata
engine-wasm/- WML parsing/runtime semantics
- navigation/history/focus behavior
- render list generation
browser/- host UI, input wiring, and Tauri command boundaries
- transport-first fetch flow and handoff to engine contracts
Hard Isolation Rules
transport-rust MUST NOT:
- implement rendering/runtime semantics
- own browser UI/session state
engine-wasm MUST NOT:
- perform network requests
- parse WBXML in TS/UI boundaries
browser MUST NOT:
- implement WML parser/runtime logic
- bypass transport contract semantics
Contract-First Files
engine-wasm/contracts/wml-engine.tsbrowser/contracts/transport.ts
Behavior changes that cross boundaries must update contracts and docs in the same change.
Runtime Parity Policy
Engine runtime behavior must stay equivalent across native Rust and WASM targets for:
- deck load and metadata handling
- navigation/focus behavior
- render output
- script invocation side effects
Target-specific glue is allowed only at serialization/binding boundaries.
Testing Expectations
- Add/adjust unit tests in the layer where behavior changed.
- Add integration/E2E coverage for cross-layer flows (
fetch_deck->loadDeckContext-> render). - Preserve deterministic outputs for fixtures and error paths.
- For stable host-visible engine/runtime behavior, check the shared
engine-wasm/examples/sourcecorpus and add or update an adjacent executable*.flow.jsonstory when the acceptance path is deterministic. - Validate executable stories by work-item/spec ID with
pnpm test:story <work-item-or-spec-id>(orall). Prosetesting-acalone remains manual evidence and must not be reported as executable coverage.
Security and Robustness Notes
- Treat host/runtime boundaries as strict IPC contracts.
- Transport errors must remain structured and deterministic.
- Runtime/script failures must trap without crashing host process.
Duplication Policy
This is the canonical duplication policy, referenced by docs/agents/RUST_ENGINE_STEERING.md and
docs/agents/RUST_TRANSPORT_STEERING.md. It exists because “keep changes localized, avoid broad
refactors” (see Scope Control in AGENTS.md) was being read as license to copy-paste instead of
share, across every layer of this repo. Sharing code is not a broad refactor when it stays inside
the boundary you’re already working in; it only becomes one when it would require touching files
you would not otherwise touch for the task at hand. Three tiers, narrowest to widest:
- Same file or module. Before adding a new function, loop, or encode/decode block, grep the file (and sibling files in the same directory) for a near-identical existing implementation. If one exists, extract a shared helper and have both call sites use it — this is always in scope, regardless of how small the requested change is.
- Same language, different crate in this workspace (e.g.
transport-rustandengine-wasm/engine, both Rust). If the same logic is genuinely needed in both, do not copy it across the crate boundary; pull it into a small shared internal crate instead. This tier has not come up yet in practice — the two crates currently have no overlapping logic — but the rule exists so the first occurrence doesn’t get copy-pasted by default. - Cross-language (Rust and TypeScript). Single source of truth lives in Rust; the TypeScript
artifact is generated, never hand-maintained. This is already the house style for wire types
(
ts-rs-derived contract types, drift-checked viapnpm --dir browser run contracts:check) and for runtime behavior (engine-wasm/enginecompiles once to both native and WASM targets rather than reimplementing logic in TypeScript). It is not yet the house style for label/taxonomy tables — seedocs/waves/MAINTENANCE_WORK_ITEMS.mdM1-23for the tracked gap. Any new cross-language constant/label/taxonomy mapping should extend the existing codegen pipeline rather than add another hand-copied table.
When duplication is found but fixing it falls outside the current task’s touched files (tier 2 or
3, or a tier-1 case in a file you have no other reason to open), record it as a scoped
docs/waves/MAINTENANCE_WORK_ITEMS.md entry instead of leaving it undocumented.
Codegen & Supported-Tooling Policy
This exists because of M1-18: transport-rust/src/native_fetch.rs hand-rolled its own WSP wire
codec instead of calling into the already-existing, spec-conformant network::wsp module —
built one day apart by the same author, never wired together, and the drift sat unnoticed for
months. Two related but distinct failure modes to guard against:
- Hand-rolling a format/protocol implementation that already has a supported one. Before
writing a new parser, encoder, decoder, or serializer for any wire format, file format, or data
shape, check whether this repo already has a module/crate for it, or whether the ecosystem has
a standard crate for it (e.g.
ts-rsfor Rust-to-TypeScript type generation,wasm_bindgenfor the WASM boundary,network::wsp’s codec for WSP framing). Use the existing one. If it’s missing a capability you need, extend it — don’t build a second implementation “for now”; “for now” is howM1-18happened, and it took an explicit owner override to unwind. - Data/logic that must stay in sync across languages or across multiple hand-authored call
sites should be generated from one source of truth, not hand-copied. Ask three questions
before deciding: (a) does this need to exist in more than one place or language, (b) is there
already a canonical source of truth it could derive from (a Rust struct/enum, a spec, a
schema), (c) would hand-sync realistically drift given how infrequently this file gets touched
and by how many different contributors/agents. If yes to those, generate it — through the
repo’s existing generator/derive infrastructure (
ts-rs,wasm_bindgen,browser/scripts/generate-contract-wrappers.mjs, drift-checked viapnpm --dir browser run contracts:check) rather than inventing a parallel one-off script. If no existing pipeline fits, design the new one deliberately and document it as a contract-first surface perAGENTS.md, rather than bolting on an ad hoc generator nobody else knows exists.
Shipping a hand-rolled reimplementation of something the repo already has a supported
implementation for is not an acceptable “small, localized change” shortcut, even under Scope
Control — flag the conflict and get an explicit decision (as happened with M1-18) rather than
quietly duplicating.
Agent Behavioral Rules
Agents MUST:
- preserve layer boundaries
- prefer localized, contract-first changes
- keep behavior deterministic and test-backed
- keep completed backlog artifacts immutable; add linked follow-up tickets instead of rewriting
donetickets when new compliance gaps are found - commit only from a feature branch; never commit on
mainorgh-pages - create a new branch name when the preferred feature-branch name already exists but is stale, unrelated, or otherwise unsuitable
- dedupe near-identical logic within files/modules already being touched rather than deferring it — see
docs/agents/RUST_ENGINE_STEERING.md/docs/agents/RUST_TRANSPORT_STEERING.md§0 for where in-scope dedup ends and an out-of-scope broad refactor begins
Agents MUST NOT:
- move network behavior into
engine-wasm - move WBXML parsing into TypeScript/Tauri frontend code
- introduce broad cross-layer refactors without explicit request
- use branch-name collisions as justification for committing directly on
main/gh-pages
GitHub Authentication and PR Publishing
Treat these as three independent authentication paths:
- local Git credentials used by
git fetchandgit push - local GitHub CLI credentials used by
gh - the Codex GitHub connector installation and its repository permissions
A successful push does not prove that the connector can create a pull request, and a connector
403 Resource not accessible by integration does not prove that local gh authentication is
invalid.
For local interactive development on macOS:
- Prefer
gh auth login -h github.com -p https -wso the OAuth credential is stored in the system keyring. - Run
gh auth setup-gitwhen Git operations should use the same keyring-backed credential. - Keep
GH_TOKENandGITHUB_TOKENunset unless an explicit headless workflow requires one; either variable overrides storedghcredentials. - Never place token values in repository files, shell startup files, logs, comments, or agent responses.
Before declaring GitHub authentication invalid:
- Ensure the command has outbound access to
api.github.com; request the required sandbox or network permission when necessary. - Run
gh auth status -h github.com. - Confirm API access with
gh api user --jq .login. - Check only whether token environment variables are present; never print their values.
Do not treat a sandboxed network failure as token revocation. If the connector cannot create a
pull request but local gh is valid, use gh as the fallback.
Before creating a pull request:
- Resolve the repository, base branch, and current head branch from local Git.
- Check for an existing pull request for the head branch with
gh pr list --repo <owner/repo> --head <branch> --state all. - Create a draft pull request by default unless the user explicitly requests ready-for-review status.
- Use a body file with real Markdown newlines for CLI-created pull requests.
- Report the resulting pull request URL and whether it is draft or ready for review.
Backlog Lifecycle Policy
- Do not change the status of an existing
doneticket totodo/in-progressduring later audits. - If an implemented ticket is found incomplete or spec-inaccurate, create a new ticket that:
- references the original
doneticket inDepends Onand notes - scopes only the corrective delta
- carries explicit spec references and acceptance checks
- references the original
- Preserve historical ticket intent and implementation traceability; corrections happen through additive follow-up work items.
Documentation Source Scope Policy
- For documentation audits and source extraction, default to active documentation paths.
- Exclude
archive/paths and date-stamped historical snapshots unless explicitly requested by the user. - If archived docs are requested, treat them as historical evidence only and do not let them override active spec/traceability docs.