Skip to content

[BLOCKER] No Dart tdbin codec generator or runtime — --to dart emits ADTs with no serialization at all #68

Description

@MelbourneDeveloper

--to dart emits ADTs but there is no Dart tdbin codec generator and no Dart tdbin runtime, so Dart is the one generated language that cannot use tdbin at all. Rust has crates/tdbin + typediagram encode/decode; TypeScript has typediagram-core/tdbin + generateTypeScriptModule (partial, #62); Dart has neither half.

The consumer

Axon is a Dart coding-agent runtime plus AXON, its node protocol (agents, tools, services, transports). Dart is the primary implementation language: protocol SDK, agent core, LSP server, CLI, and Flutter desktop client are all Dart; the VS Code extension is TypeScript. We have made tdbin the specified primary serialization for the protocol and for passing objects between layers, so the missing Dart half is the single blocker for the whole design.

Today typediagram --to dart contracts/agent_state.td gives us plain classes with no serialization surface whatsoever — not even toJson/fromJson:

class StateIndexes {
  final String summary;
  final List<double>? embedding;
  final List<String> files;
  final String? toolName;
  final bool isError;
  final int? exitCode;
  final int round;
  final int? durationMs;

  const StateIndexes(this.summary, this.embedding, /* ... */);
}

So every boundary hand-rolls JSON. Our agent-state store is the worst case: it carries an opaque payloadJson: String field precisely because there is no generated codec to encode the real payload, and that state is append-only and unbounded (gigabytes expected by design), re-encoded on every append and re-parsed on every rationalizer query.

What is needed

  1. A Dart tdbin runtime package on pub.dev — the peer of crates/tdbin and typediagram-core/tdbin. Generated codecs must compile against a published package; a path dependency into a typeDiagram checkout is not viable for a pub-published consumer (same shape as [BLOCKER] Publish the tdbin Rust runtime crate — generated codecs reference a crate that exists nowhere #60 for Rust).
  2. A Dart tdbin codec generator — the peer of packages/typediagram/src/converters/typescript-tdbin.ts, emitting a StructCodec-equivalent per generated type, against the ADTs --to dart already produces.
  3. A CLI path to emit it. typediagram encode/decode are Rust-only today, and TypeScript tdbin codec: no CLI command, no List<T>, no scalar Option<T>, number not bigint #62 notes TypeScript has no CLI command either. Whatever form the fix for TypeScript tdbin codec: no CLI command, no List<T>, no scalar Option<T>, number not bigint #62 takes, please cover Dart in the same surface, and in --config outputs (--config cannot emit tdbin outputs — codecs go stale under --watch #65) so codecs cannot go stale under --watch.

Schema shapes we need supported

Our committed contract (packages/axon/axon_agent/contracts/agent_state.td, abridged) exercises most of the shapes listed as unsupported in the TypeScript boundary:

alias ElementId = String

union ElementKind { session task attempt round message toolCall toolResult artifact }

type StateIndexes {
  summary: String
  embedding: Option<List<Float>>
  files: List<String>
  toolName: Option<String>
  isError: Bool
  exitCode: Option<Int>
  round: Int
  durationMs: Option<Int>
}

union StateQuery {
  SemanticQuery { text: String limit: Int }
  FileQuery { path: String limit: Int }
  AttributeQuery { isError: Option<Bool> exitCode: Option<Int> round: Option<Int> limit: Int }
  RecentQuery { limit: Int }
}

Concretely that means Dart needs:

  • Struct-form union variants (SemanticQuery { text: String limit: Int }) — the same shape [BLOCKER] tdbin rejects struct-form union variants — verify fails on the first union in a real schema #61 reports verify rejecting. Every one of our query unions uses it; without it our coverage is 0%.
  • List<T> and Option<List<T>>embedding: Option<List<Float>> is the hot path (vector index), and it is list-shaped by nature.
  • Scalar Option<T>Option<String>, Option<Int>, Option<Bool> all appear.
  • Bare-variant unions (ElementKind) — currently generated as a Dart enum; the codec needs to agree with that representation.
  • alias to a scalar (ElementId = String) used as a field type.

Dart-specific representation questions

  • Int width. Dart native int is 64-bit, but on the web it is a JS double with a 53-bit safe range. TypeScript chose number and rejects out-of-range values (TypeScript tdbin codec: no CLI command, no List<T>, no scalar Option<T>, number not bigint #62 asks for bigint). Please state the Dart mapping explicitly, and if int is used, reject out-of-range on encode rather than silently truncating.
  • Floatdouble, and BytesUint8List, presumably; worth pinning in the spec.
  • Errors as values, not exceptions. The TypeScript codec returns a Result and keeps diagnostics as values. Please do the same in Dart rather than throwing — a Result-shaped return (sealed success/error union) is what the ecosystem's typed-error packages expect, and thrown errors would force every call site into try/catch.
  • Frame parity. We need the framed and packed-framed forms with the schema compatibility hash, not just bare bodies — our messages cross process and network boundaries (stdio, WebSocket, TCP), so decode must be able to reject a missing or mismatched hash.

Interop matrix that matters to us

Dart ↔ TypeScript byte compatibility is a hard requirement: the VS Code extension talks to the Dart LSP server, so both ends must agree on the exact bytes for the shapes above. Golden vectors shared across Rust, TypeScript, and Dart would be the useful acceptance criterion.

Happy to test against a branch — we have a real schema, a real multi-process boundary, and a Dart implementation waiting on this.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions