Ddot.it is a small language for knowledge graphs. It is meant to be used within existing files, which have their own structure and syntax. We call it the host language, where ddot.it is the guest language. Ddot.it has a generic triple model and a simple syntax. A gentle introduction can be found at ddot.it.
-
Last Modified: 2026-07-28
-
Version: 1.2.0
Introduction
-
What is the Purpose of ddot.it?
-
Where can I use it? Guest and Host Language
-
What can I express structurally with ddot.it? Information Model
-
How do I type ddot.it syntax? Syntax Concepts
Purpose
Ddot.it is meant to be spread over many files. Together, all these files should be parsed by a ddot.it parser, creating one large knowledge base. This knowledge base can interlink and describe concepts, and it can annotate the documents in which the ddot.it syntax has been typed. Crucially, ddot.it is meant to be typed alongside other content in another syntax. In fact, it has been designed specifically so that it coexists with other syntaxes: Markdown and AsciiDoc syntax have no meaning in ddot.it and also the other way round.
Guest and Host Language
-
Ddot.it can be used as a custom text format for representing knowledge graphs.
-
Ddot.it can be used as a guest language inside another host language or even a more complex host system. For example, ddot.it can be used within Markdown and AsciiDoc. In general, a host language SHOULD be parsed by a host-language aware parser. That parser SHOULD strip away host syntax for encoding host structures. For example,
## Foodefines a headline in Markdown, so onlyFooshould be given to the ddot.it parser.
Information Model
Ddot.it has a simple, triple-based information model.
-
Subject (S, about what are we stating facts),
-
Relation (P, named after RDF property/predicate), and
-
Object (O, another objects name or a data value).
Additionally, a triple can have any number of attached Meta Relation (MR) and Meta Object (MO) pairs. This resembles RDF 1.2 / RDF-Star and provides a way to state facts about the triple.
In the information model a Triple always has all three of Subject, Relation and Object. The AST shows Subject and Relation as 0..1 because the syntax may leave them out — an omitted subject is inherited from the previous line and an omitted relation is the implicit links to. The subject is filled in by the time a triple reaches this model; the relation is filled in by the consumer, because the serialised form omits it rather than writing links to out. |
Triple Events
The information model above says what a triple is. This section says how a parser reports one. There is exactly one wire format, the triple event, and it is normative: an implementation conforms by emitting these bytes.
Dirk ..works at.. SAP ,, ..since.. 2016{"from":"Dirk","type":"works at","to":"SAP","meta":[{"type":"since","to":"2016"}],"kind":"asciidoc","source":"file:///Users/maxvolkel/Desktop/example.adoc","location":1}Syntax Concepts
Ddot.it tries to use few syntax elements, to make learning, reading and writing easy. The following syntax elements are used:
- Double dot (
..) -
It’s used for writing triples. This most fundamental syntax element, the double dot (ddot) is what gave ddot.it its name.
ExampleClaudia ..owns.. cat
- Whitespace
-
Space and tab are significant but never their exact amount. So ddot.it has a middle ground between 'whitespace is optional' (its not) and 'indentation level matters' (it doesn’t).
- Double comma (
,,) -
For appending meta data to a triple.
ExampleClaudia ..owns.. cat ,, ..since.. X-mas 2026
- Double exclamation mark (
!!) -
For commands.
- Newlines
-
These are significant as they terminate a triple. A triple is recognised by its
../..markers, so ddot.it does not add a way to abort or continue a triple across a newline. The one place a newline is otherwise required — stating several (relation, object) pairs about one triple — has an inline substitute, the double semicolon. - Double semicolon (
;;) -
Used only inside a triple’s meta part (introduced by
,,), where it separates several (relation, object) assertions about that triple on a single line — the inline form of writing one per line in a,,block. In a subject, relation, or object,;;is ordinary text with no special meaning.
Syntax Shorthands
To avoid repeating the same subject over and over, ddot.it allows to omit it after the first line. So instead of
sss ..ppp1.. ooo1 sss ..ppp2.. ooo2 sss ..ppp3.. ooo3
one can write
sss ..ppp1.. ooo1 ..ppp2.. ooo2 ..ppp3.. ooo3
| There is no shorthand for expressing multiple objects for the same subject-relation combination. |
Commands
Commands extend ddot.it capabilities in various ways while keeping the extra syntax small.
All commands can be typed in several ways, which are interpreted identically:
-
ddot.it/COMMAND_NAME -
!!COMMAND_NAME
The !!COMMAND_NAME is easy to type, whereas the https://ddot.it/COMMAND_NAME is the most self-documenting version. In practice, ddot.it/COMMAND_NAME provides the best balance between readability and self-documenting. The expansion from !! could be done by editor plugins at typing time or ddot.it CLI tools. |
All commands are designed to be resolvable HTTPS URI (in their ddot.it/COMMAND_NAME form). Each URI resolves to a documentation page for the exact command. In the future, that page could even explain the interpretation of parameters. |
The following are two example URIs and their component parts:
foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:noseA ddot.it command thus consists of
-
command-begin := (
https://ddot.it/|http://ddot.it/|ddot.it/|!!) -
command := command-begin command-name uri-query? uri-fragment?
-
command-name := one or more characters that are not
WS,NL,?or
(the name stops at?/; the command does not — see below) -
uri-query :=
?then any characters up toWS,NLor# -
uri-fragment :=
#then any characters up toWSorNL
Commands fall into two groups by when they act:
- Special Pre-Parse Commands
-
These three commands must be respected at the ddot.it parser level:
!!off,!!on, and!!block. - Regular In-Text Commands
-
!!this, and any otherddot.it/<name>.
!!label was an in-text command in earlier versions of ddot.it. It is retired: label is now an ordinary relation (foo ..label.. bar), defined in the Vocabulary Specification. This changes nothing at the syntax level — see Spec. |
Pre-Parse Commands
They are recognised while a chunk is split into snippets, before the snippet grammar runs. They decide which text is parsed (!!off/!!on) or taken verbatim (!!block), and so never appear as nodes in the AST.
A host-aware parser is a different matter. If an AsciiDoc-aware ddot.it parser strips // comment lines before handing text over, the commands inside them never arrive. Steering that relies on comments therefore works with a plain ddot.it parser reading the raw file, and a host-aware parser SHOULD preserve pre-parse commands it finds in host comments rather than discarding them. |
Command !!off
Ddot.it has no comments in its syntax. But only conforming lines are interpreted as triples. To explicitly exclude certain lines from interpretation — effectively like a block comment — ddot.it provides ddot.it/off and ddot.it/on commands.
This command, like all commands, is not active when inside a Command !!block. |
Command !!on
This command, like all commands, is not active when inside a Command !!block. |
See also: The !!off command.
Command !!block
JSON, for example, allows to type foo\"bar to effectively have a single double quote in the string between foo and bar and get foo"bar as a value. In ddot.it, quotes don’t need to be escaped, as they carry no semantics in the syntax. Other character sequences, such as .., ,,, ;;, !! or newlines cannot be used everywhere.
Ddot.it has no escape syntax. Instead, ddot.it provides a !!block command. The block command allows almost any character to be put verbatim into ddot.it, without interpreting it. By default, a !!block ends with the first blank line, but a custom end marker line can be defined.
John ..address.. !!block
1 Broad Way
Lower Manhatten
..No !!command has an effect here..
U.S.A.
Dirk ..works at.. SAPJohn ..address.. !!block?end=END
1 Broad Way
Lower Manhatten
..No !!command has an effect here..
U.S.A.
END
Dirk ..works at.. SAP-
S=
John, P=address, O=1 Broad Way
Lower Manhatten
..No !!command has an effect here..
U.S.A. -
S=
Dirk, P=works at, O=SAP
!!block in other fields!!block?end=EOS
../../notes.adoc
EOS
..has type.. relative path
water ..formula.. H2O ,, ..note.. !!block
solid, liquid, gas -- all three
phases occur naturally
Dirk ..works at.. SAP-
S=
../../notes.adoc, P=has type, O=relative path
(a subject containing..— impossible to type directly, sinceSubjectexcludesDT2. The..has type..line continues the logical line after theEOSterminator.) -
S=
water, P=formula, O=H2O, Meta:note=solid, liquid, gas — all three
phases occur naturally -
S=
Dirk, P=works at, O=SAP— a fresh line: plain text cannot continue the completedwatertriple.
This shows the difference between syntax and information model. In the information model every field value may contain newlines, .., ,,, and other otherwise-special sequences. At the syntax level those cannot be typed directly into their field (Object := TextExcept{NL,CM2}, Subject := TextExcept{NL,DT2,DT4}, …); the !!block wrapper is what puts them there verbatim. A subject containing .., for instance, can only be written via !!block. |
The ?end= Parameter
!!block takes one optional command parameter, end, naming the line that closes the block. It is an ordinary URI query, so all four command spellings accept it: !!block?end=END, ddot.it/block?end=END, and the http(s):// forms.
The marker appears twice and both occurrences are tagged block-end, which is what visually pairs an opener with its terminator:
john ..address.. !!block?end=END
^^^^^^^ ^^^^^ ^^^
command | block-end (the marker in the opener)
command-param
1 Broad Way
still inside: a blank line does not close a marked block
END
^^^ block-end (the terminator line)
Dirk ..works at.. SAP!!block is a pre-parse command, so none of this appears in the AST: by the time the snippet grammar runs, the block’s lines have already become the value of the position the opener fills (any of the four). That is why the grammar has no production for it, and why the highlighter treats the body as its own verbatim region rather than as field content. See the golden corpus: 17-block (blank-line form) and 21-block-end-marker (marked form).
The golden corpus pins three of the four opener positions: 17-block and 21-block-end-marker (Object, both terminator forms), 29-block-subject (Subject, with a DT2 continuation line) and 30-block-meta-object (MetaObject). The meta text position has no case yet; one should be added. |
See also https://ddot.it/block.
In-Text Commands
They occur inside a triple field and are part of that field’s text (see Abstract Syntax Tree). A strict source-to-source tool leaves them untouched; interpreting tools give them their meaning.
A ddot.it parser can ignore these commands. They are handled by higher layers interpreting the triple+meta stream. New commands can be used without changing this part of the spec.
Command !!this
To annotate the document in which the ddot.it is typed, a built-in command, !!this, can be used.
-
It is replaced with the
sourceUriof the chunk in which it is written, when the ddot.it syntax is to be interpreted. -
Usually used as Subject.
-
A strict source-source processor (e.g. a linter) should leave the
!!thisas it is.
See also https://ddot.it/this.
Parsing Process Overview
Now we know the Information Model and Syntax Concepts, we look into the process from file on disk to triples in memory.
We have several goals in parsing:
-
Extract triples (and their meta and sourceUri) from a chunk.
-
Syntax-highlight text in a chunk. For this, we need to tag each character into a set of nested but non-overlapping regions. We must classify characters into a tree. Then, we define styling via CSS for each region and thus define the syntax highlighting.
-
Auto-completion support. Building on tagged regions, we need to define for each substate (in which region is the cursor, what’s before and after the cursor, how we got at this state: by typing or moving the cursor?) whether to launch auto-completion or not. And what to show.
| As all goals are interwoven, we explain them together. The purpose of this spec is to clearly define the parse semantics. Actual implementations for fast parsing don’t need to construct tagged regions for every character. |
-
First, we must get chunks (text blocks) to be parsed from the system in which ddot.it has been entered.
-
Then we pre-parse the chunk into snippets — and the excluded parts. This needs to carefully process both !!block (verbatim) and !!off/!!on (exclude) commands.
Chunks: Getting Text for Parsing
-
Ddot.it can appear in any text box, in any system or file. Getting the text out of a system in general requires help from the host system (e.g. Atlassian Confluence) to deliver a documents’ text (and its sourceUri) to the ddot.it parser.
-
Plain
.ddottext files can be parsed directly. Markdown and AsciiDoc documents can also be parsed with a plain ddot.it parser, but specialised parsers offer more features. -
More complex formats such as HTML, Java source code or PowerPoint slides SHOULD be parsed with format-specific ddot.it parsers.
| This is the same principle the Highlight Specification applies at D9, where the injection grammar deliberately ships with no marker precondition and lets the line-shape gate decide. Parsing and highlighting agree: look at everything, and let the syntax speak for itself. |
-
Usually, one file is one chunk. Some files, like YAML files, can encode multiple documents in a single file. In a wiki, each page could be one chunk.
-
The host parser must deliver one chunk for each such part.
-
Each chunk must be given its own sourceURI, which is often derived from file URI plus an appended hash fragment. This fragment can be a chunk index or start line number. Line numbers help tracing back the origin of unintended triples. The
sourceUriis also what allows ddot.it to annotate parts of a document via the !!this command.
Each pair of text chunk plus URI is the given to the ddot.it parser.
Splitting Chunks to Snippets
A chunk MAY be split into smaller snippets if it contains !!off and !!on commands. Otherwise, all the chunks’ content is the snippet. In any case, the pre-parser must be careful in respecting the interleaving of both Command !!block and Command !!off/Command !!on.
A stateful parser must remember if it is in plain ddot.it or in verbatim ddot.it. When in plain, the off/on commands have an effect and bring the parser into the excluded state.
Parsing a Snippet
This is where the meat of the parsing happens.
-
Parsing is defined via the Lexical Layer (via Regexes), a Grammar in EBNF (via an EBNF) and a Parse State Automaton.
-
Then we group and rename parser states into tagged regions to define syntax highlighting.
In case of doubt, the golden corpus at ddot.it/test-data/cases/ shall have the last word.
Lexical Layer
Only the following basic tokens have a special role when parsing ddot.it.
| Token | Name | Comment | Regex |
|---|---|---|---|
WS | Whitespace | TAB or any Unicode space separator — see Spec: what counts as | [\t\p{Zs}] |
NL | Newline | CR, LF, or CR+LF | (\r\n|\r|\n) |
DT | Dot | A single dot | \. |
CM | Comma | A single comma | , |
SC | Semicolon | A single semicolon | ; |
EM | Exclamation Mark | A single exclamation mark | ! |
The syntax of ddot.it is fundamentally based on counting consecutive characters. The main element is the double-dot (..) which is only valid if it is not preceded or followed by another dot. One or three dots are fine and can be used in text as usual, without any special interpretation. Four dots, well, we made that a special case of the empty relation, so that has a defined interpretation. Five dots are fine and mean nothing.
And here we define the counted token sequences.
The lookarounds are what express "neither preceded nor followed by another one of the same character" — the counting rule cannot be written with a quantifier alone. \.{2} on its own would happily match the first two dots of …, which is exactly what Tokenization rule forbids. These are the same forms the Highlight Specification uses, and they are normative. |
| Token | Description | Regex |
|---|---|---|
DT2 | A sequence of 2 consecutive |
|
DT4 | A sequence of 4 consecutive |
|
CM2 | A sequence of 2 consecutive |
|
SC2 | A sequence of 2 consecutive |
|
EM2 | A sequence of 2 consecutive |
|
TX | Any maximal sequence of characters that contains no | (see Tokenization rule) |
Grammar in EBNF
-
Terminals are UPPERCASE
-
Variables are TitleCase
-
?optional,*zero or more,+one or more,|alternative,()grouping -
//comments about the syntax itself. ddot.it has no comments syntax.
-
TextExcept{…}is a shorthand for:-
We have text, that does not start or end with whitespace (
WS). -
We allow all of
(NL | WS | DT2 | DT4 | CM2 | SC2 | EM2 | TX)except those listed in the Except clause. -
E.g.:
TextExcept{CM2,NL} = (DT2 | DT4 | SC2 | EM2 | TX) (WS* (DT2 | DT4 | SC2 | EM2 | TX))*.
-
Snippet := Line*
// Ordered choice: a Line is a Triple if one can be derived, otherwise NotATriple.
// NotATriple is therefore never reachable for text that forms a Triple.
Line := WS* Triple WS* NL
| NotATriple NL
// Any run of tokens on one line. Reached only when the Triple alternative failed.
NotATriple := (WS | DT2 | DT4 | CM2 | SC2 | EM2 | TX)*
DoubleDot := WS* DT2 WS*
// the untyped (empty) relation, written '....' or '.. ..'.
// WS+, not WS*: with no WS between, the two DT2 would be four adjacent dots,
// which lexes as DT4 — that is the first alternative, not the second.
QuadDot := WS* ( DT4 | DT2 WS+ DT2 ) WS*
Triple := Subject?
( DoubleDot Relation DoubleDot | QuadDot )
Object
WS* Meta?
Subject := TextExcept{NL,DT2,DT4}
Relation := TextExcept{NL,DT2,DT4}
Object := TextExcept{NL,CM2}
Meta := CM2 WS* MetaInline WS*
// the block may be empty: ',,' NL WS* ',,'
| MetaBlockOpen NL (MetaBlock NL)? WS* CM2 WS*
// The ',,' that opens a block may sit at the end of the triple line, or alone
// on the line *after* it. Both attach the block to the same triple.
MetaBlockOpen := CM2
| NL WS* CM2
MetaRelation := TextExcept{NL,DT2,DT4}
MetaInline := ( MetaTripleInline (SC2 MetaTripleInline)* )
| MetaTextInline
MetaTripleInline := DoubleDot MetaRelation DoubleDot MetaObjectInline
| QuadDot MetaObjectInline
MetaObjectInline := TextExcept{NL,SC2}
MetaTextInline := (CM2 | SC2 | EM2 | TX)
(WS* (DT2 | DT4 | CM2 | SC2 | EM2 | TX))*
MetaBlock := MetaTripleInBlock (NL MetaTripleInBlock)*
| MetaTextBlock
MetaTripleInBlock := DoubleDot MetaRelation DoubleDot MetaObjectInBlock
| QuadDot MetaObjectInBlock
MetaObjectInBlock := TextExcept{NL}
MetaTextBlock := MetaTextBlockLine (NL MetaTextBlockLine)*
MetaTextBlockLine := WS*
// a real line must contain anything besides just CM2 and WS
// and may not be DT2 to avoid parsing as MetaTripleInBlock/MetaRelation
(TX | EM2 | SC2 )+
(WS | TX | DT2 | DT4 | EM2 | CM2 | SC2 )*-
SC2(;;) is special only inside the,,meta part, where the inline form uses it to separate several (relation, object) assertions about one triple (seeMetaInline). In aSubject,Relation, orObjectit is ordinary text — a triple is recognised by its../..markers, so ddot.it never uses;;to terminate or abort a triple. -
Objectis mandatory and non-empty (TextExceptrequires at least one token):a ..b..with nothing after the closing..is not a Triple. -
Subject?is optional: an omitted subject means continue with the subject of the previous line. -
Textis a whitespace-trimmed sequence; theWSaround and between words is not part of the Subject, Relation, or Object contents. -
QuadDotis the shorthand for an implicit relation:a….banda .. .. bboth meana ..links to.. b.The two spellings are the same operator, not two operators — see10-spaced-untypedand02-untyped-linkin the golden corpus. -
MetaBlockOpenhas two spellings, and they mean the same thing. The block form’s opening,,may end the triple line (04-multiline-meta,14-multiline-meta-text) or stand alone on the next line (09-standalone-comma-block). Only the block form allows the second spelling — the inline form’s,,must stay on the triple line, since what follows it on that same line is the meta.The second spelling is where the grammar would be ambiguous without ordered choice and greedy optionals: a lone
,,after a complete triple could equally be that triple’s block opener or aNotATripleline. The two rules settle it:Meta?is greedy, so aMetathat can be derived is derived, and the,,attaches.Two properties keep this from over-reaching:
-
MetaBlockOpen’s second alternative is `NL WS* CM2—WSnever includes a newline. The,,must therefore sit on the line immediately after the triple; a blank line in between breaks the attachment. -
The block form only matches if a closing
,,is also found. A lone,,after a triple with no closing,,derives noMetaat all, so the greedy choice falls through and that line isNotATriple— as is any,,that follows no triple.
-
Abstract Syntax Tree
What a parsed ddot.it Snippet looks like as a tree.Every arrow is a contains relation; there are no other relations in this diagram.Orange nodes are exclusive choices: exactly one of their children is present.
-
Lineis either aTripleor aNotATriple.NotATripleis the second alternative of theLineproduction: text that is passed through unparsed. It has no children. -
Subjectis absent when it is continued from the previous Line. -
Relationis absent for theQuadDotshortcut; it then meanslinks to. -
Metais either inline or block, never both. LikewiseMetaInlineandMetaBlockeach hold either triples or text, never both. -
MetaObjectunifiesMetaObjectInlineandMetaObjectInBlock: the two differ only in which characters may occur, not in what they mean once parsed. -
MetaTextInlineandMetaTextBlockcarry the built-in relationtext, so in the Information Model they are a Meta like any other. -
A
Commandis a part of the text of the field holding it, not a replacement for it: in!!this ..author.. Johnthe Subject holds one Command, whereas inJohn ..text.. 42no field holds a Command.!!off,!!on, and!!blockare Pre-Parse Commands and never appear as AST nodes. Each in-text command may equally be writtenddot.it/this,http://ddot.it/thisorhttps://ddot.it/this. -
Commands may occur in every field of a Triple and of a Meta alike:
Subject,Relation,Object,MetaRelationandMetaObject.
Parse State Automaton
Red arrows indicate the 'happy path': a simple Triple without any Meta.
-
StartOfLineis both the start and the accepting state: aSnippetis a sequence ofLine, and eachLineends by returning here. A Triple is emitted on the transition back. -
NotATripleimplements the second alternative ofLine: text that is not a Triple is consumed up to the nextNLand passed through unparsed. -
NLnever entersNotATriple. Every transition consumes its token, so a… -→ NotATriple : NLwould consume the newline and then haveNotATripleeat the following line — one bad line would swallow its innocent successor. A failure detected onNLmeans the line is already over, so those transitions go toStartOfLineand are annotatedemit Line as NotATriple.NotATripleis entered only on a non-NLtoken, where there really is a rest of the line left to consume. -
Where a state fails on
NLinside a,,block, the whole multi-line construct is abandoned and every line it spans is emitted asNotATriple— see Unterminated Constructs for what "abandoned" means operationally. -
The
DT4shortcut skips theRelationstate entirely, sinceQuadDotcarries the implicit relationlinks to. -
The same holds in the meta part:
MetaTripleInline := QuadDot MetaObjectInlineandMetaTripleInBlock := QuadDot MetaObjectInBlockemit the same implicitlinks toas the meta relation — "implicit MetaRelation" in parts 2 and 3 of the automaton means exactly that, and there is no separate meta-only default. See corpus case24-untyped-inline-meta. -
MetaStartis the only place where the inline and the block form are told apart: anNLright after theCM2selects the block form, anything else the inline form. -
A block can also be opened from
StartOfLine, one line later than the triple it belongs to (MetaBlockOpen’s second spelling). `MaybeBlockOpenis the one-token lookahead this needs: a line-initialCM2is a block opener only if anNLfollows it, otherwise it is just the start of aSubjectthat happens to begin with,,. Compare09-standalone-comma-block(opener) with `09’s own first line (an ordinary triple). -
MetaTextInlineandMetaTextBlockLineare entered only on a token other thanDT2/DT4. This mirrors the grammar: once meta text has started, a laterDT2is text, not a separator (see Example 2). -
Inside a
,,-block, triples and text cannot be mixed.BlockFirstLinecommits the whole block: a leadingDT2/DT4selectsBlockTripleLine(whereTX/EM2/SC2at line start is an error), plain text selectsBlockTextLine(whereDT2/DT4at line start is an error). This matchesMetaBlock, which is either allMetaTripleInBlockor allMetaTextBlockLine. -
The closing
CM2is recognised only at the start of a line, after optionalWS, fromBlockFirstLine,BlockTripleLineorBlockTextLine. ACM2anywhere else in a block line is content. -
A block may be empty: reaching the closing
CM2straight fromBlockFirstLinegives,,NLWS*,,with no Meta at all. -
The object must hold at least one token.
ObjectStartskips leadingWS; anNLorCM2before any object token makes the line aNotATriple, matching the mandatoryObjectin the grammar. -
SC2(;;) is plain text everywhere in the triple line (Subject,RelationStart,Relation,ObjectStart,TripleObject,NotATriple). It becomes a separator only in the inline,,meta form (part 2), whereMetaObjectInline -→ MetaNextInlineonSC2divides several (relation, object) assertions about one triple; in the block meta form (part 3) newlines do that job and;;is content.
Unterminated Constructs
A ,, block needs a closing ,,, and a !!block needs its terminator (a blank line, or the ?end= marker). What happens when the closing token never arrives has to be stated once, because parsing and highlighting answer it differently and both answers are correct for what they do.
Only the parse result is normative for the triples a tool extracts. The divergence is confined to presentation, and is visible only for input that is malformed to begin with.
References
-
The Pomsky portable regex syntax seems smart