Hello, World

The smallest useful Tln program finds something in your data and flags it. Here we flag any greeting record and greet it by name — Prolog on the left, Tln on the right:

ISO Prolog (1995)
% Facts loaded externally; shown here as plain facts:
greeting(1, 'World').

hello(Id) :-
    greeting(Id, Name),
    format("Hello, ~w!~n", [Name]).
Tln
detect "Hello, World" {
  for records where type == "greeting"
  flag matching items
  label "Hello, {attr.name}!"
}

Line by line:

  • detect "Hello, World" — a block. detect finds records matching a condition and flags them.
  • for records where type == "greeting" — the selector: which records this block runs over.
  • flag matching items — flag every record that matched.
  • label "Hello, {attr.name}!" — a template; {attr.name} interpolates that record’s name.

Optional clauses like priority LOW|MEDIUM|HIGH|CRITICAL can tune how loudly a result is surfaced, but they aren’t required.

Test it

Tln has a built-in test framework. Facts live in a given block, and you assert on the result:

test "greets the world" {
  given {
    record 1 type "greeting"
    attr 1 "name" "World"
  }
  when detect "Hello, World"
  expect {
    flagged 1
    label contains "Hello, World!"
    count == 1
  }
}

Run it:

tln test hello.tln hello.tln.test
# ==> hello.tln.test: 1 test(s)
#
# 1 passed, 0 failed

The other classic: family trees

Every Prolog tutorial has the family tree — a handful of facts and a recursive ancestor. Tln splits this the way production systems already do: the parent relation is data (loaded as facts, never written in .tln) and the derivations are rules:

ISO Prolog (1995)
parent(tom, bob).
parent(bob, ann).

grandparent(X, Z) :- parent(X, Y), parent(Y, Z).

ancestor(X, Z) :- parent(X, Z).
ancestor(X, Z) :- parent(X, Y), ancestor(Y, Z).
Tln
// facts:  record(family, "bob", person, ...)
//         attr(family, "bob", "parent", "tom")

detect "Children of Tom" {
  for records where type == "person"
    and attr "parent" == "tom"
  flag matching items
  label "{item.name} is a child of Tom"
}

One-hop relationships are ordinary blocks. The transitive ancestor closure — a recursive rule — runs on Tln’s recursive Datalog resolver (the same machinery behind the built-in category_tree) and terminates even on cyclic graphs. See Derived predicates & recursion.

The mental model

Facts are your data — and you never write them in .tln. They are loaded from your existing systems (ERP, CRM, asset management, a warehouse DB, MCP tools…) as generic entity–attribute records:

record(Entity, Id, Type, Category, Status, Date)   // the thing
attr(Entity, Id, Name, Value)                        // a property of the thing

Rules are your knowledge. You write blocks that reason over those facts:

BlockAnswers
definea reusable named condition
rule“Is this allowed?” (allow / block)
detect“What pattern exists?” (flag + label)
recommend“What should we do next?”
combine“What’s the optimal mix?”
predict forecast classify cluster findbuilt-in ML — see Beyond Prolog
workflow on collect enrichact on the world via MCP — see MCP & workflows

The engine is native Go. Tln does not interpret rules ad-hoc: a lexer → parser → validator → planner turns each block into a deterministic query plan ([]PlanStep) that an executor runs against a pluggable FactStore. Same facts in, same decision out — every time, and every decision traces back to the exact rule that fired.

This is Expert-in-the-Loop: instead of a human (or an LLM) re-deciding every case, a deterministic expert system decides, and people are reserved for the rare case that truly needs them. In production that engine is the decision core of OpenTalon — the LLM handles intent and language, Tln handles knowledge and inference.

The CLI

tln build  rules.tln          # parse, validate, and show the query plan
tln test   rules.tln t.tln.test   # run .tln.test assertions
tln run    rules.tln --seed t.tln.test   # evaluate against a FactStore
tln explain rules.tln t.tln.test  # trace why each result fired
tln repl                       # interactive: :load, :eval, :trace

Next: the Language Reference, or see Tln beside the language it modernizes in Prolog → Tln.