Most integration failures I have chased were not hard problems. They were small disagreements nobody wrote down. One team measured a clearance in millimetres, another read the same number as inches. One service promised a reply that another service never waited for. A component needed a signal that nothing upstream ever emitted. Each of these is trivial on its own. They cost weeks because the agreement between the parts lived in a slide, in a comment, in someone's memory, in a diagram that had already drifted out of date. It lived everywhere except somewhere a machine could check it. So I wrote a small notation for putting those agreements where a script can read them back, and I have released it, free and open. It is called HashTag.

An integration is only as sound
as the agreements you can actually check.

01 Where systems actually break

Systems are built by composing parts, and composition breaks at the joins: the places where one part relies on, emits to, or is understood by another. We are good at testing each part in isolation. We are bad at writing the contract between two parts in a form anything can verify. That contract is usually prose, and prose does not fail a build.

The result is a predictable class of defect. Not a bug inside a component, but a mismatch across the boundary between two of them, discovered late, on real hardware, when the two finally meet. The fix is almost always small. The cost comes from finding it at the worst possible time, because nothing checked the agreement earlier, when it was still cheap.

I wanted the agreement itself to be an artifact: something you write down once, in plain text, that a script can read and answer questions about, before anything is built.

02 Write the fact, ask the question

HashTag is deliberately small. Seven symbols, each with one job. You state a fact, you ask a question, and you record where a fact came from, all in the same grammar. It reads like the documentation you wanted anyway, and it runs like a check.

tag
#car:vehicle .doors[4]
#car |has|/part #engine_1:engine

#car |has|/part ?part      // which parts does the car have?

The one firm rule is honesty about ignorance. If the graph does not know something, the answer is unknown, never false. That sounds like a small semantic point. It is the thing that makes the notation safe to lay across many parts that each know only their own corner of a system. A part that has simply not spoken yet never reads as a contradiction, which is exactly what you need while a picture is still being assembled.

◉
Open-world by default

Missing knowledge is reported as unknown, not as false. A component you have not described yet is silent, not wrong.

03 The question that catches the gap

Here is the check I care about most. Given a set of components that each declare what they provide and what they need, ask the graph: which component needs a service that no part provides?

tag
?c |needs| ?s
? |does|/provide ?s <unknown>

Read the second line as "no party is recorded as providing this." It is a set difference over the recorded relations, an absence rather than a claim. Until recently the language could ask this of a simple property but not of a relation, so this one soundness check, the most common one I hit in real work, had to be written in host code every time. The 2.1 release closes that gap: the language answers it directly, and it stays honest about scope, reporting only what a party actually holds rather than asserting anything about the wider world.

This is not a toy example for me. In the open sensor-fusion and integrity monitor I have been building in public, the components are sensors, an estimator, a monitor, and a supervisor, each declaring the messages it produces and consumes. The absence query is what tells me a consumer is expecting a signal that no producer emits, before a single node runs. The mismatch that would otherwise surface as a silent gap on the bench becomes a one-line question with a definite answer at design time.

The 2.1 release also lets a statement carry a small, verifiable record of where it came from: a digest, a signer, a timestamp, alongside the fact itself. The language only carries those fields. Checking them is a separate job. But it means an agreement stays checkable as it travels between parties, which matters the moment more than one team is writing down facts that have to line up.

04 Try it

The specification is open under CC BY 4.0, the reference toolchain is Apache-2.0, and the reference parser is a single Python file with no dependencies. It lints .tag files, validates them against the spec, and resolves references, and it runs entirely on your machine. You can read exactly what it does, or build from it, in an afternoon.

The notation earns its place by being small. It does one thing: it makes the agreements between parts explicit enough that a script can tell you whether an assembly holds together, before you find out the hard way. The open tools I am building all speak it, so they can be checked against each other and not only tested alone. That is the next thread I will write up. The language is here now if it is useful to you.

The short version

HashTag writes the agreements between parts of a system in plain text, so a script can tell you whether an assembly holds together before you build it. Open spec, open tooling, no dependencies.