Skip to content

10. Descriptions

A description is a bare string in any body — module, surface, interface, view, process. Renderers treat description text as markdown with two extensions: cross-references ([[…]]) and value interpolation (@field / @@aspect). These appear everywhere descriptions are shown: hover tooltips, completion docs, side panels, widget bodies — and standalone .md files use the exact same dialect (Chapter 10a).

module Payments {
aspect team: "Payments"
aspect {
domain: "Payments"
security.zone: "PCI"
}
"
# Payments
Core payment processor for the **@@domain** domain, operating
in the *@@security.zone* zone. Published events are consumed
by [[Orders]] and [[Notifications]].
> Every transaction in the platform flows through this service.
"
interface authorize
interface refund
}

The description renders with markdown formatting, with @@domain and @@security.zone substituted, and with [[Orders]] and [[Notifications]] rendered as clickable links to the respective modules.

A description has two legitimate shapes; pick whichever fits the element.

Mode 1 — a short one-liner. Say what the name alone doesn’t, and stop.

Mode 2 — a longer doc. Full Markdown: internals, an API overview, design notes — a real document if the element warrants one.

What’s not legitimate is restating the obvious. Don’t echo what the name, the nested modules, the interfaces, or the processes already say.

Rule. A redundant description is worse than none. For a PaymentProcessor, “Processes payments” is worse than an empty description — it adds length without adding information. No description at all is completely fine when there’s nothing non-obvious to add.

Section titled “Structured links belong in fields, not prose”

A description is for prose. A link to an external spec or doc — a Confluence page, an OpenAPI document, a data catalog entry — is a property of the element, and properties go in fields (Chapter 9):

module Orders {
repo.url: "https://github.com/acme/orders"
docs.url: "https://wiki.acme.com/orders"
api.spec: "https://specs.acme.com/orders/openapi.yaml"
"Order intake, validation, and fulfilment hand-off."
}

Putting these in fields keeps them structured and queryable — tooling can render them as buttons and the model becomes a links hub. A longer Markdown description may still carry the occasional inline link in its prose, but the canonical “where the spec lives” pointer is a field.

These CommonMark constructs are supported:

ConstructExample
Bold**bold**
Italic*italic*
Strikethrough~~struck~~
Inline code`code`
Headers# H1, ## H2, ### H3
Listsordered, unordered, nested
TablesGFM-style pipe tables
Blockquotes> note
Code blockstriple-backtick fenced, optional language
Horizontal rule---
External links[text](https://...)

These are not supported and are either rendered as literal text or stripped:

  • Raw HTML (security boundary — renderers sanitize aggressively).
  • Images (descriptions are text; visuals belong in widgets and views).
  • Footnotes, definition lists, task lists.
  • Auto-linking of bare URLs (use explicit [text](url)).

Headers (#, ##, ###) all render at the same visual rank. Descriptions have one header level; depth carries no semantic weight. You’re free to use multiple # for source readability, but don’t rely on visual hierarchy between H1 and H3.

The reason: descriptions appear in tooltips, side panels, and other constrained contexts where reproducing a full document hierarchy looks wrong. Treat headers as section titles, not as nesting.

[[…]] links to another declaration in the model. Two forms:

By stable ID:

"Routes to [[#r3n8wt]] for downstream processing."

Always resolves if the target exists in the workspace. Renders as the target’s human-readable name, linked to its declaration site.

By human-readable name:

"Published events are consumed by [[Orders]] and [[Notifications]]."

Resolves against the whole workspace. If two declarations share the name, the reference is ambiguous — the renderer marks it as an error and the LSP emits a diagnostic listing the candidates. Resolve ambiguity by switching to the ID form.

Names can be namespaced; the namespaced form is matched whole:

"See [[Personal.Banking.Payments]] for the legacy path."

References that fail to resolve render as error markers and emit LSP warnings — typos surface immediately rather than rotting silently.

Inside the brackets, a leading @@ marks an aspect key — a plane (Chapter 9) — rather than an element name:

"Runs in [[@@security-zone:pci]]; part of the [[@@domain]] plane."
  • [[@@key]] references the whole plane named by the aspect key (. separates key-path segments).
  • [[@@key:value]] references a specific place on it — the aspect — where : always introduces the value.

These resolve to the set of elements carrying that aspect and link to the aspect overlay, not to one declaration.

A [[Doc#section]] reference points at a #-header inside an .md document (Chapter 10a); bare [[Doc]] links to the document as a whole.

Reading an aspect off another node: [[ref]]@@path

Section titled “Reading an aspect off another node: [[ref]]@@path”

[[ref]]@@aspectPath is a cross-reference composed with the value getter below: it reads an aspect on the referenced node instead of the owning one.

"Part of the [[#pay001]]@@domain domain." // reads the domain aspect off Payments

There’s no bracketed [[Name@field]] form — foreign value access is always the composed [[ref]]@@aspectPath, and because it carries its own context it works everywhere, including standalone .md files.

Every element keeps an automatic “mentioned in” index of all [[…]] references pointing at it, across descriptions and .md documents. Tooling surfaces it on the element, so you can jump from a declaration to everywhere it’s discussed.

A getter substitutes a value into the description of the same node it’s attached to. The sigil names the axis: @path reads a field, @@path reads an aspect — mirroring the key: value fields and aspect { } blocks the paths are drawn from (Chapter 9).

module Payments {
version: "1.2"
aspect {
domain: "Payments"
sla.tier: "gold"
}
"Version @version. SLA tier: @@sla.tier. Operates in the @@domain domain."
}

Renders as something like: “Version 1.2. SLA tier: gold. Operates in the Payments domain.”version is a field, read with one @; sla.tier and domain are aspects, read with @@.

Resolution walks the type chain — if the node doesn’t declare the field/aspect itself but inherits it from a parent type, the inherited value is used.

@@ reads aspects on the owning node, never on referenced nodes (the same locality holds for a bare @ field read):

module A {
aspect { domain: "Sales" }
"Domain: @@domain" // resolves to "Sales"
}
module B {
aspect { domain: "Ops" }
"Other module's domain: @@domain" // resolves to "Ops" (B's own aspect),
// NOT A's domain
}

A bare @@ on module A cannot read aspects on module B — for that, use the foreign getter [[B]]@@domain (above). And because a bare getter needs an owning node, it is description-only: in a standalone .md document (which has no owning node) a bare @ or @@ is an error, so documents use [[ref]]@@aspectPath exclusively (Chapter 10a).

If @@aspectPath doesn’t resolve, the renderer outputs the literal sentinel <missing:aspectPath> and the LSP emits a warning at the interpolation site. Silent fallback to empty string is forbidden — typos and stale references have to be visible. (The same rule holds for a missing @field.)

module C {
"Owner: @@team" // no 'team' aspect declared or inherited
}

Renders as: “Owner: <missing:team>” with an LSP warning.

A body may have multiple bare strings. They join with \n in declaration order:

module Payments {
"First paragraph about the service."
aspect team: "Payments"
"Second paragraph, declared after the team aspect. Order in the source
doesn't matter for resolution but does matter for description joining."
interface authorize
}

This lets long descriptions span multiple string literals without forcing one giant multi-line string.

A real description from the Payments demo:

module #k7m2qx Payments {
aspect team: "Payments"
docs.runbook: "https://wiki.acme.com/pci"
aspect {
domain: "Payments"
security.zone: "PCI"
criticality: "High"
}
"
Core **payment processing** service for the *@@domain* domain.
Operates in the `@@security.zone` zone with criticality *@@criticality*.
> Every transaction in the platform flows through this service
> before reaching an external processor.
**Capabilities.** Authorize, capture, and refund transactions;
route to processors via [[#r3n8wt]]; publish `paymentEvents`
consumed by [[Orders]] and [[Notifications]].
**Compliance.** PCI-DSS scope.
"
interface authorize
interface refund
interface paymentEvents
}

That renders as a full markdown card with aspect substitutions and live cross-references to #r3n8wt, Orders, and Notifications. Note the runbook URL is a field (docs.runbook), not a link buried in the prose — structured links stay structured.

The cross-reference resolver and aspect interpolator are part of the core package — not LSP-only. Both the LSP (for diagnostics and hover) and client-only viewers (for rendering) consume the same resolver. The result: descriptions look identical wherever they’re displayed.

  • Descriptions are bare strings, treated as markdown with two ArchLang extensions.
  • [[#id]] and [[Name]] link to declarations; [[@@key:value]] links to an aspect, [[Doc#section]] to a document heading.
  • @field substitutes a field, @@aspect substitutes an aspect — both on the same node (description-only); [[ref]]@field / [[ref]]@@aspect reads a value off another node and works everywhere.
  • Every element gets automatic backlinks (“mentioned in”) spanning descriptions and .md files.
  • Headers all render at one rank; raw HTML and images are not supported.
  • Missing references and missing aspect paths surface as errors, never silently.
  • Multiple bare-string descriptions concatenate.
  • The same dialect renders standalone .md documents — a first-class archspace surface (Chapter 10a).

Chapter 10a: Markdown Documents →.md files as a first-class archspace surface, sharing this exact flavored-markdown dialect.