---
title: "Semantic Identity for Agent Edits in Rust"
canonical: https://wavect.io/blog/semantic-identity-rust-agent-edits/
language: en
description: "How SEMAPRAX uses Rust types, stable declaration IDs, checked HIR, deterministic graphs and replayable evidence to constrain coding-agent edits."
image: "https://wavect.io/img/blog/headers/header_semantic-identity-rust-agent-edits.png"
---

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

9 min read · 23 Aug 2026 Last reviewed August 23, 2026

[**Next**](/blog/programming-languages-matter-less-ai/)

# Why Agent Edits Need Semantic Identity: Building SEMAPRAX in Rust

TL;DR

Text positions are fragile edit targets because formatting and nearby changes move them. SEMAPRAX instead gives declarations persistent identities, resolves source into checked HIR, projects deterministic graph views and binds patches to a revision. Rust newtypes keep declaration and expression identities distinct, while ordered collections and independent evidence replay make the boundary reproducible. This is a pre-alpha research design, not a claim of production readiness or complete language safety.

**A coding agent should edit a declaration by identity and expected meaning, not by a line number that may already be stale.** In [SEMAPRAX](https://wavect.io/semaprax/), persistent declaration IDs survive ordinary source movement, checked HIR centralizes resolved meaning, and revision-bound patches fail closed when their source snapshot changes. Rust makes these distinctions explicit in the compiler's types.

This article explains that design as implemented in the pre-alpha research compiler. It is useful even if you never adopt the language: the same boundaries apply to refactoring engines, compiler services and agent tools that must turn an intention into a reviewable change.

## Why are line and byte offsets poor agent edit targets?

A text edit usually says “replace bytes 418 to 463.” That address describes one file snapshot, not the program entity the agent meant. A formatter, comment or concurrent edit can move it. A name is better, but overloads, scopes and renames make names contextual too.

The SEMAPRAX language contract therefore makes human-readable `.spx` source the canonical Git projection while exposing a versioned semantic graph as the preferred agent interface. Public declarations carry persistent `@id` identities. Expression identities remain revision-scoped because preserving every transient syntax node across arbitrary rewrites would imply a stronger identity guarantee than the compiler can honestly provide. The distinction is part of the [SEMAPRAX language and compiler contract](https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/docs/RFC-0001.md).

## How does Rust encode the identity boundary?

The HIR does not pass raw strings everywhere. It defines separate newtypes for declarations and expressions:

```
pub struct DeclarationId(String);
pub struct ExpressionId(String);

pub struct ResolvedFunction {
    pub id: DeclarationId,
    // checked signature, body and effects
}
```

This shortened example reflects the types in [`src/hir.rs` at the audited revision](https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/src/hir.rs). The benefit is mundane and important: a function that expects a persistent declaration target cannot accidentally receive an expression address. The compiler must perform an explicit conversion or reject the operation.

The same module stores declaration indexes in `BTreeMap` and relation sets in `BTreeSet`. Ordered collections are not sufficient for determinism, but they remove one common source of output drift: randomized hash iteration. Semantic order still has to be preserved where order affects execution.

## Why resolve once into checked HIR?

If a graph exporter, native backend and Wasm backend each reconstruct meaning from syntax, they can disagree about types, ownership or call targets. SEMAPRAX resolves and verifies first, then downstream projections consume the checked representation. The published [architecture and trust-boundary document](https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/docs/ARCHITECTURE.md) describes this pipeline and separates implemented lanes from future authority.

That gives an agent a useful chain of custody:

1. Parse human-readable source.
2. Resolve names and persistent identities into HIR.
3. Verify types, effects, ownership rules and contracts admitted by the current language subset.
4. Project deterministic graph JSON or target artifacts from checked semantics.
5. Bind proposed changes to the source revision and expected semantic target.

The graph is therefore a compiler projection, not a second source of truth. Git still reviews source. The agent gains structured context without asking humans to review an opaque database.

## What makes a semantic graph deterministic?

Determinism is a whole-pipeline property. Stable node identifiers do not help if edge order changes between runs or diagnostics depend on hash iteration. SEMAPRAX uses ordered indexes, explicit serialization, bounded traversal and canonical output rules in its [Rust graph projection](https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/src/graph.rs).

There is also a subtle prohibition: never “fix” execution vectors by sorting them for prettier JSON. Evaluation and cleanup order are semantic. Canonical sorting belongs only on mathematically unordered sets. Sequence-bearing data must retain the compiler-defined order.

## How can evidence constrain an agent patch?

A serialized evidence capsule should describe why a patch is admissible, but possession of that capsule must not grant write authority. SEMAPRAX separates proof data from the component that owns the commit. Its patch route acquires the normal lock, independently replays the exact bounded evidence, checks the current source snapshot, and only then stages and commits the candidate. The [patch-evidence implementation](https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/src/patch_evidence.rs) keeps replay and application visibly separate.

This design addresses three distinct failures:

- **Stale intent:** the patch was valid for an older source revision.
- **Wrong target:** the text still matches, but it now refers to a different declaration.
- **Forged confidence:** a caller presents a plausible report without reproducing the checks.

Independent replay does not prove that a requested feature is wise. It proves a narrower and testable statement: this bounded proposal still satisfies the compiler checks to which its evidence is bound.

## What should Rust compiler and agent-tool authors copy?

1. **Use domain newtypes.** Separate persistent entity IDs, revision-local node IDs, digests and capabilities at the type level.
2. **Keep one checked semantic core.** Make graph views and backends consume resolved meaning instead of reparsing names independently.
3. **Design determinism deliberately.** Specify ordering, serialization, diagnostics and failure behavior, then test repeat runs byte for byte.
4. **Bind edits to snapshots.** A semantic target without a source digest is still vulnerable to drift.
5. **Keep evidence powerless.** Replay evidence inside the authority boundary before staging any change.
6. **Publish the limits.** A completion matrix is more useful than a broad “implemented” label when different compiler lanes have different evidence.

SEMAPRAX follows the last rule with an evidence-gated [completion matrix](https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/docs/COMPLETION-MATRIX.md). It prevents a passing unit test for one projection from becoming a claim about every target or runtime.

## What does SEMAPRAX not prove yet?

SEMAPRAX v0.2 is experimental pre-alpha research. Its current repository documents bounded native C11/Clang and WebAssembly Core lanes, deterministic semantic tooling and a growing verified subset. It does not claim production readiness, complete memory safety, all operating systems, a complete ownership system, full ecosystem interoperability, a public Component Model runtime or live economic authority.

The practical way to evaluate it is to inspect the source and dated evidence, not extrapolate from the project goal. Start with the [SEMAPRAX architecture overview](/semaprax/architecture/), then compare the public claims with the pinned compiler revision below.

## How can you reproduce this walkthrough?

```
git clone https://github.com/wavect/semaprax.git
cd semaprax
git checkout ca339feffcadf77a679abe2f159376287cf2e22c
cargo run -- graph examples/hello.spx
```

The command inspects the graph projection at the exact revision used for this article. Run the repository's documented quality gates before treating any local modification as evidence.

*Editorial disclosure: OpenAI Codex assisted with drafting and translation. The technical claims and code references were checked against SEMAPRAX commit `ca339fe` by Wavect. The article contains no performance or safety claim inferred from model output.*

## Final thoughts

Reliable agent editing starts by naming the thing that should change, the revision in which it exists and the checks that must still hold. Rust can encode those distinctions directly: persistent declaration identity is not an expression address, checked HIR is not raw syntax, and evidence is not authority.

SEMAPRAX is an experimental implementation of that boundary. Its useful lesson today is smaller than its long-term language goal: give agents semantic handles, make every projection deterministic, and fail closed when meaning or source state has moved.

## You may also like..

[**Do Programming Languages Matter Less in the Age of AI?** A broader look at which language abstractions still matter when agents write more code.](/blog/programming-languages-matter-less-ai/) [**AI Enablement vs Generic AI Consulting** Compare an implementation-led engagement with a strategy-only consulting approach.](/compare/ai-enablement-vs-generic-ai-consultancy/)

Agent engineering

## Continue through this cluster

Coding agents, MCP, context systems, evaluation and the controls required for dependable automation.

[Start with the cornerstone**Graph Engineering for AI Agents: When Does a Knowledge Graph Pay Off?**](/blog/graph-engineering-ai-agents/)

- [OpenViking Review 2026: Is Filesystem Memory Production-Ready?](/blog/openviking-agent-memory-review/)
- [LLM-as-a-Verifier Explained: Architecture, Costs, and Production Fit](/blog/llm-as-a-verifier/)
- [TrueForge Review: Is the Open-Source Agent Harness Production-Ready?](/blog/trueforge-agent-harness-review/)
- [Agent-Readable Websites: llms.txt, Markdown Mirrors and What Breaks](/blog/agent-readable-website-llms-txt-markdown-mirrors/)
- [Localized URLs Break hreflang: Keep One English Slug](/blog/english-slugs-vs-localized-urls-hreflang/)

Inbox, without the noise

## Follow the work that matters to you

Get a short email when we publish something new. Follow the whole blog or only the problems you care about.

[**Back**](/blog/overview/)

[![Kevin Riedl](/img/team/kevin.webp)](/team/kevin-riedl/)

[Kevin Riedl](/team/kevin-riedl/) https://linkedin.com/in/wsdt

9 min read · 23 Aug 2026 Last reviewed August 23, 2026

[**Next**](/blog/programming-languages-matter-less-ai/)

New posts by email ×

×

Get new posts by email

A short email when we publish. Free, no tracking.

## Structured Data

```json
{
  "@context": "https://schema.org",
  "@graph": [
    {
      "@id": "https://wavect.io/#organization",
      "@type": [
        "Organization",
        "ProfessionalService",
        "LocalBusiness"
      ],
      "employee": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "founder": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "legalRepresentative": [
        {
          "@id": "https://wavect.io/team/kevin-riedl/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Kevin Riedl",
          "url": "https://wavect.io/team/kevin-riedl/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        },
        {
          "@id": "https://wavect.io/team/christof-jori/#person",
          "@type": "Person",
          "jobTitle": "Managing Director",
          "name": "Christof Jori",
          "url": "https://wavect.io/team/christof-jori/",
          "worksFor": {
            "@id": "https://wavect.io/#organization",
            "@type": [
              "Organization",
              "ProfessionalService",
              "LocalBusiness"
            ]
          }
        }
      ],
      "name": "Wavect GmbH",
      "subjectOf": {
        "@id": "https://wavect.io/verified-claims.json#dataset",
        "@type": "Dataset",
        "creator": {
          "@id": "https://wavect.io/#organization",
          "@type": [
            "Organization",
            "ProfessionalService",
            "LocalBusiness"
          ]
        },
        "description": "A machine-readable registry of quantitative and qualitative claims published by Wavect, with review dates, localized page appearances and public third-party citations where available.",
        "inLanguage": "en",
        "isAccessibleForFree": true,
        "license": "https://creativecommons.org/licenses/by/4.0/",
        "name": "Wavect verified publication claims",
        "url": "https://wavect.io/verified-claims.json"
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/team/kevin-riedl/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Kevin Riedl",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796365",
        "https://www.linkedin.com/in/wsdt",
        "https://github.com/wsdt"
      ],
      "url": "https://wavect.io/team/kevin-riedl/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/team/christof-jori/#person",
      "@type": "Person",
      "jobTitle": "Managing Director",
      "name": "Christof Jori",
      "sameAs": [
        "https://www.wikidata.org/wiki/Q139796367",
        "https://www.linkedin.com/in/jocr77/",
        "https://github.com/jo-chris"
      ],
      "url": "https://wavect.io/team/christof-jori/",
      "worksFor": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      }
    },
    {
      "@id": "https://wavect.io/#website",
      "@type": "WebSite",
      "inLanguage": [
        "en",
        "de",
        "es",
        "zh"
      ],
      "name": "Wavect",
      "potentialAction": {
        "@type": "SearchAction",
        "query-input": "required name=search_term_string",
        "target": {
          "@type": "EntryPoint",
          "urlTemplate": "https://wavect.io/search/?q={search_term_string}"
        }
      },
      "publisher": {
        "@id": "https://wavect.io/#organization",
        "@type": [
          "Organization",
          "ProfessionalService",
          "LocalBusiness"
        ]
      },
      "url": "https://wavect.io/"
    },
    {
      "@id": "https://wavect.io/blog/semantic-identity-rust-agent-edits/#webpage",
      "@type": "WebPage",
      "dateModified": "2026-08-23",
      "inLanguage": "en",
      "isPartOf": {
        "@id": "https://wavect.io/#website",
        "@type": "WebSite"
      },
      "lastReviewed": "2026-08-23",
      "url": "https://wavect.io/blog/semantic-identity-rust-agent-edits/"
    }
  ]
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  "abstract": "Text positions are fragile edit targets because formatting and nearby changes move them. SEMAPRAX instead gives declarations persistent identities, resolves source into checked HIR, projects deterministic graph views and binds patches to a revision. Rust newtypes keep declaration and expression identities distinct, while ordered collections and independent evidence replay make the boundary reproducible. This is a pre-alpha research design, not a claim of production readiness or complete language safety.",
  "articleBody": " Blog overview/AI and agents/Agent engineering Why Agent Edits Need Semantic Identity: Building SEMAPRAX in Rust TL;DR Text positions are fragile edit targets because formatting and nearby changes move them. SEMAPRAX instead gives declarations persistent identities, resolves source into checked HIR, projects deterministic graph views and binds patches to a revision. Rust newtypes keep declaration and expression identities distinct, while ordered collections and independent evidence replay make the boundary reproducible. This is a pre-alpha research design, not a claim of production readiness or complete language safety. A coding agent should edit a declaration by identity and expected meaning, not by a line number that may already be stale. In SEMAPRAX, persistent declaration IDs survive ordinary source movement, checked HIR centralizes resolved meaning, and revision-bound patches fail closed when their source snapshot changes. Rust makes these distinctions explicit in the compiler's types. This article explains that design as implemented in the pre-alpha research compiler. It is useful even if you never adopt the language: the same boundaries apply to refactoring engines, compiler services and agent tools that must turn an intention into a reviewable change. Why are line and byte offsets poor agent edit targets? A text edit usually says “replace bytes 418 to 463.” That address describes one file snapshot, not the program entity the agent meant. A formatter, comment or concurrent edit can move it. A name is better, but overloads, scopes and renames make names contextual too. The SEMAPRAX language contract therefore makes human-readable .spx source the canonical Git projection while exposing a versioned semantic graph as the preferred agent interface. Public declarations carry persistent @id identities. Expression identities remain revision-scoped because preserving every transient syntax node across arbitrary rewrites would imply a stronger identity guarantee than the compiler can honestly provide. The distinction is part of the SEMAPRAX language and compiler contract. How does Rust encode the identity boundary? The HIR does not pass raw strings everywhere. It defines separate newtypes for declarations and expressions: pub struct DeclarationId(String); pub struct ExpressionId(String); pub struct ResolvedFunction { pub id: DeclarationId, // checked signature, body and effects } This shortened example reflects the types in src/hir.rs at the audited revision. The benefit is mundane and important: a function that expects a persistent declaration target cannot accidentally receive an expression address. The compiler must perform an explicit conversion or reject the operation. The same module stores declaration indexes in BTreeMap and relation sets in BTreeSet. Ordered collections are not sufficient for determinism, but they remove one common source of output drift: randomized hash iteration. Semantic order still has to be preserved where order affects execution. Why resolve once into checked HIR? If a graph exporter, native backend and Wasm backend each reconstruct meaning from syntax, they can disagree about types, ownership or call targets. SEMAPRAX resolves and verifies first, then downstream projections consume the checked representation. The published architecture and trust-boundary document describes this pipeline and separates implemented lanes from future authority. That gives an agent a useful chain of custody: Parse human-readable source. Resolve names and persistent identities into HIR. Verify types, effects, ownership rules and contracts admitted by the current language subset. Project deterministic graph JSON or target artifacts from checked semantics. Bind proposed changes to the source revision and expected semantic target. The graph is therefore a compiler projection, not a second source of truth. Git still reviews source. The agent gains structured context without asking humans to review an opaque database. What makes a semantic graph deterministic? Determinism is a whole-pipeline property. Stable node identifiers do not help if edge order changes between runs or diagnostics depend on hash iteration. SEMAPRAX uses ordered indexes, explicit serialization, bounded traversal and canonical output rules in its Rust graph projection. There is also a subtle prohibition: never “fix” execution vectors by sorting them for prettier JSON. Evaluation and cleanup order are semantic. Canonical sorting belongs only on mathematically unordered sets. Sequence-bearing data must retain the compiler-defined order. How can evidence constrain an agent patch? A serialized evidence capsule should describe why a patch is admissible, but possession of that capsule must not grant write authority. SEMAPRAX separates proof data from the component that owns the commit. Its patch route acquires the normal lock, independently replays the exact bounded evidence, checks the current source snapshot, and only then stages and",
  "articleSection": "Engineering",
  "author": {
    "@id": "https://wavect.io/team/kevin-riedl/#person",
    "@type": "Person",
    "name": "Kevin Riedl",
    "sameAs": [
      "https://www.wikidata.org/wiki/Q139796365",
      "https://www.linkedin.com/in/wsdt",
      "https://github.com/wsdt"
    ],
    "url": "https://wavect.io/team/kevin-riedl/"
  },
  "citation": [
    {
      "@type": "WebPage",
      "name": "SEMAPRAX language and compiler contract",
      "url": "https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/docs/RFC-0001.md"
    },
    {
      "@type": "WebPage",
      "name": "src/hir.rs at the audited revision",
      "url": "https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/src/hir.rs"
    },
    {
      "@type": "WebPage",
      "name": "architecture and trust-boundary document",
      "url": "https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/docs/ARCHITECTURE.md"
    },
    {
      "@type": "WebPage",
      "name": "Rust graph projection",
      "url": "https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/src/graph.rs"
    },
    {
      "@type": "WebPage",
      "name": "patch-evidence implementation",
      "url": "https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/src/patch_evidence.rs"
    },
    {
      "@type": "WebPage",
      "name": "completion matrix",
      "url": "https://github.com/wavect/semaprax/blob/ca339feffcadf77a679abe2f159376287cf2e22c/docs/COMPLETION-MATRIX.md"
    }
  ],
  "dateModified": "2026-08-23",
  "datePublished": "2026-08-23",
  "description": "Text positions are fragile edit targets because formatting and nearby changes move them. SEMAPRAX instead gives declarations persistent identities, resolves source into checked HIR, projects deterministic graph views and binds patches to a revision. Rust newtypes keep declaration and expression identities distinct, while ordered collections and independent evidence replay make the boundary reproducible. This is a pre-alpha research design, not a claim of production readiness or complete language safety.",
  "headline": "Why Agent Edits Need Semantic Identity: Building SEMAPRAX in Rust",
  "image": "https://wavect.io/img/blog/headers/header_semantic-identity-rust-agent-edits.svg",
  "inLanguage": "en",
  "keywords": "Rust, Compilers, AI Agents",
  "mainEntityOfPage": {
    "@id": "https://wavect.io/blog/semantic-identity-rust-agent-edits/",
    "@type": "WebPage"
  },
  "publisher": {
    "@id": "https://wavect.io/#organization",
    "@type": [
      "Organization",
      "ProfessionalService",
      "LocalBusiness"
    ]
  },
  "url": "https://wavect.io/blog/semantic-identity-rust-agent-edits/",
  "wordCount": 1467
}
```

```json
{
  "@context": "https://schema.org",
  "@type": "BreadcrumbList",
  "itemListElement": [
    {
      "@type": "ListItem",
      "item": "https://wavect.io/",
      "name": "Home",
      "position": 1
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/overview/",
      "name": "Blog overview",
      "position": 2
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/topics/ai-agents/",
      "name": "AI and agents",
      "position": 3
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/clusters/agent-engineering/",
      "name": "Agent engineering",
      "position": 4
    },
    {
      "@type": "ListItem",
      "item": "https://wavect.io/blog/semantic-identity-rust-agent-edits/",
      "name": "Semantic Identity for Agent Edits in Rust | ",
      "position": 5
    }
  ]
}
```
