SYMMARTYS — public-record facts for agents that trade
🔄 Synced from the AI-Factory monorepo (append-only history). Pull requests are welcome — merged PRs are imported back and re-synced here. Issues · Pull requests
SYMMARTYS 0.5.0 is live at symmartys.modelmarket.dev: product symmartys, capabilities symmartys.*, Python package aimarket-symmartys. Updates to GitHub, PyPI, GHCR and MCP Registry are not yet published. See migration and state preservation.
[Python >=3.11] [Ed25519 + ML-DSA signing] [MIT]
🌐 English · Русский · Español · Français · 中文
An agent that buys, sells or pays needs facts no model can make up: is this VAT number valid,
who is this company, what was the euro rate that day, is this wallet on a sanctions list, and
can I prove this document existed yesterday. SYMMARTYS answers each from the public record —
the European Commission, GLEIF, the ECB, the U.S. Treasury, the EU, the UK and Bitcoin — and
signs every answer together with where it came from, when, and the SHA-256 of what the source
returned.
Symmartys (συμμάρτυς): a joint or corroborating witness — a source-backed answer that another party can check.
Since 0.2.0 it also answers the questions that come next: is this company in its national
register, is its EORI number valid for customs, how old is the domain the shop runs on, which day
is the fifth business day in Bavaria or on TARGET2 — and it gives agents two tools no model has:
a web page captured as evidence (anchored in Bitcoin) and a timer, including a dead man's
switch, that calls the agent back with a signed event.
Capabilities
Trust, briefly
- Evidence in every answer — the source URL, the time it was fetched and the SHA-256 of the
bytes it returned, plus the source's licence and attribution.
- Two signatures — the gateway's signed receipt (oracle-core, Ed25519, hybrid ML-DSA when
ORACLE_PQC=1) and the request-bound X-Provider-Signature the hub verifies.
- A source that cannot answer costs nothing — the answer is
ok:false, and the hub debits
nothing for ok:false: a member state's VAT registry that is down, sanctions lists not loaded or
older than 48 h, fewer than two timestamp calendars, a block the header chain does not reach yet.
- Outbound requests are allowlisted per source; redirects are followed by hand and only to
that source's own hosts; bodies are size-capped.
- The two capabilities that call a URL the caller names (web capture and timer callbacks)
reach public addresses only: http(s) on ports 80/443, every address of the name checked (no
loopback, private, link-local, CGNAT or metadata ranges), the connection pinned to the checked
address, every redirect checked again, and at most 20 captures of one host per minute.
Examples
Through the hub, the same calls are paid and receipted: POST https://modelmarket.dev/ai-market/v2/invoke
with "product_id":"symmartys".
VAT: the consultation number
VIES issues a consultation number only to a valid EU VAT number named as requester. It is the
requester's proof, kept by the requester: pass your own number as requester_vat and the
consultation number in the answer is evidence, in your name, that you checked your counterparty
at checked_at. Without it the check is still made; the answer says how to get the number.
FX: which day
A rate "on" a date is the latest ECB publication on or before it, and the answer names that day
(ecb_date): a Sunday is priced at Friday's rate, visibly. A currency the ECB stopped quoting
(RUB since March 2022) is refused rather than answered with a years-old rate.
Sanctions: a lookup, not a determination
A wallet is matched exactly (EVM and bech32 addresses case-insensitively). A name is matched as a
set of words — order, case, accents and legal forms ignored ("DOE, John" is "John Doe"), a word
within a typo counts 0.9 and never reads as the same name, and every asked word found in a longer
name (a patronymic, a middle name) still matches. The default threshold is 0.85; each hit says
which name matched and its score.
A hit is a reason to look closer; a miss is not a clearance. Names collide and aliases are
incomplete. Every answer says so, and names the lists it used with their publication dates and
SHA-256. An outdated list is refused, not used.
UN designations come through the UK and EU lists. The UN Security Council's own file carries
no reuse licence, so SYMMARTYS does not read it. The UK Sanctions List (Open Government Licence
v3.0) gives every UN designation the UK implements its UN reference number, as the EU file does
for some: a hit carries un_ref, and lists: ["UN"] screens only the UK and EU entries that have
one. On 2026-10-17 the three lists together held all 1010 UN entries (991 by UN number in the UK
list, the rest by name in the EU and OFAC lists). The newest UN decisions reach them with the
delay the UK and the EU take to implement them, usually days.
Timestamps
The proof is a standard OpenTimestamps file: it verifies with any OpenTimestamps client, without
SYMMARTYS. The reference example (hello-world.txt) verifies to block 358 391, 2015-05-28.
Web capture: what the page said
SYMMARTYS fetches the page once, as a browser would, and returns a record: the URL asked, the
final URL, every redirect with its status and IP, the time, the status, the headers that say what
the page was (content type, length, Last-Modified, ETag, Date, Server…), the TLS
certificate the server presented (subject, issuer, validity, names, SHA-256 of the certificate),
the page title and body_sha256. The body itself comes back as body_b64 when it is 512 KB or
less (the cap for a page is 5 MB). record_sha256 is the SHA-256 of the record as JSON with sorted
keys and compact separators, and it is anchored in Bitcoin through OpenTimestamps — verify it later
with symmartys.timestamp.verify@v1. A 404 is a capture (that is what the page said); a host that
cannot be reached, or a certificate that does not verify, costs nothing.
Timers and the dead man's switch
at (an ISO time with zone) or after_seconds sets a one-off call instead. The event carries the
timer id, kind, due and fired times, the attempt and your payload (up to 4 KB); the signature is
SYMMARTYS's key from its signed manifest, over the event as JSON with sorted keys and compact
separators. The token is kept only as its SHA-256. Timers live at most 90 days, in one SQLite file
in SYMMARTYS's data volume.
Company registers, EORI, domains, business days
- Company registers answer from the register itself: Norway's Brønnøysund Register Centre
(NLOD 2.0), France's SIRENE and national register through the State's open API (Licence Ouverte
2.0), the Czech ARES, Finland's PRH (CC BY 4.0) and — where the operator has set
COMPANIES_HOUSE_API_KEY, a free key — the UK's Companies House (OGL v3.0); without the key GB
is not offered. Numbers are checked locally first (Norwegian, Czech and Finnish check digits; a
French SIRET is reduced to its SIREN). Statuses: active, ceased/dissolved/deleted, bankrupt, in
liquidation, in re-organisation.
- EORI comes from the European Commission's EOS service; "not valid" is an answer.
- RDAP is asked only at the servers IANA's bootstrap file names for the TLD (https only); a
subdomain or URL is reduced to the registered name by asking the registry. Some country codes
(
.de, .ru…) publish no RDAP service yet — that is refused and costs nothing.
- RDAP etiquette. Registries' terms forbid high-volume automated querying and repackaging, so
SYMMARTYS asks one registry at most 30 times a minute, keeps each answer 24 hours (an hour for
"not registered"; a cached answer says
cached: true and keeps the original fetch time), and
passes the registry's own notices — its terms of service among them — through as
registry_terms.
- Personal data. Registers list sole traders too, and their entries are personal data whatever
the register's licence says. Every company answer carries
personal_data: the purpose
(verifying a counterparty), the basis (legitimate interest, GDPR Art. 6(1)(f)) and the retention
(SYMMARTYS keeps no copy; its call log holds only a hash of the input).
- Business days come from python-holidays compiled into SYMMARTYS: no network, nothing to go
down; the answer names the library version, because a holiday declared after that release is
not in it.
Honest limits
- VIES answers what the member state's registry answers at that moment; some states return no
name or address. SYMMARTYS stores nothing.
- GLEIF covers entities that have an LEI — many small companies do not.
- Reference rates are for information, not dealing.
- Sanctions screening is a lookup in three public lists, not legal advice or a compliance
programme; other jurisdictions' lists are not included.
- A timestamp proves existence no later than a block's time, not authorship.
- A web capture shows what the server answered SYMMARTYS at that moment; another visitor, country
or cookie may be shown another page. It is not a statement that the content is true.
- Registers differ in what they publish; the registers of four countries (five with the UK key) are covered so far; Poland's KRS API did not answer when tried.
- A timer is delivered at least once if the callback answers 2xx within four attempts; a receiver
should treat a repeated
timer_id with the same due_at as the same event.
Run it
In the aicom monorepo the same runs from its root with
SYMMARTYS_VOLUME_NAME=symmartys-data docker compose -f symmartys/docker-compose.yml up -d --build (volume migration; fresh installs first run docker volume create symmartys-data). SYMMARTYS_PUBLIC_URL is the address
buyers reach; it is written into the signed manifest.
Production, from the aicom monorepo: ./scripts/deploy_symmartys.sh --remote <host> serves it at
https://modelmarket.dev/providers/symmartys/ behind the existing nginx (one snippet, backed up,
nginx -t before reload), keeps the running image as symmartys:prev and rolls back an unhealthy
build. Steady memory ≈ 200 MB; the container is capped at 768 MB. It runs as uid 10001.
The public homepage and direct API are at https://symmartys.modelmarket.dev/. The Hub keeps its canonical
provider URL https://modelmarket.dev/providers/symmartys for federation and signed
source_hub requests; both addresses reach the same provider. The dedicated-host
nginx template serves the landing,
assets and API while keeping /ops/ private.
Tests: pip install -e "vendor/oracle-core[pqc]" -e '.[dev]' && pytest (in the monorepo:
-e ../oracles/core) — 175 tests on excerpts of real answers, none reaching a live source; in the
monorepo the signed canonical is also checked byte for byte against the hub's own function.
Sources and licences
Code: MIT — see LICENSE. The OpenTimestamps reference library it uses is LGPL-3.0.