How a home enters mkan by being written in Slack — Hermes hears every message in #mkan, the words become a Twenty record, the level that makes it eligible, and the yes that puts it live. Drawn first in plain words: built, blocked, next.

Home is the lane that turns a few words typed into Slack into a home for rent on mkan.sd. The scene it is built for: you go to meet a host, sit with him, ask what you need to know to list his places, and on the way back you type it into #mkan from your phone — a name, a phone number, rooms, a price, a neighbourhood — in any order, in Arabic or English. The channel is your field notebook; the engine does the rest. The engine reads every message in #mkan, decides on its own whether it is a home, makes a record of it in the Twenty CRM, tells you what it understood and what is still missing, and rings a bell when the home has enough to go live. Then you say live, and it is on the site.

The one rule that outranks speed: the words are yours, the record is a mirror of them, and nothing goes live without your yes. The engine never invents a price, a room, or a phone number. What it did not understand it keeps as your words, verbatim, on the record — and every correction you make teaches it your way of writing.

How a home travels

Eight steps. You touch three of them (1, 5, 7); the rest happen on their own.

What each box means

BoxIn plain words
1 · You write a home in SlackOne message in #mkan, typed after meeting the host. A name, a phone, "two rooms, a hall, a bathroom, a kitchen". No form, no order, no special word to start with. A numbered list is several homes of the same host.
2 · Hermes hears every messageThe gateway on this Mac is woken by every message in the channel and runs one command. It never reads the words itself and never answers in its own voice — it is the ear, not the brain.
3 · The words become a home in TwentyA script reads your message straight from Slack, asks Claude what it says, checks the answer against the CRM's own field list, and creates one home record per unit — with a code like 0005-01 once it knows the host. Your original words are kept on the record as a note.
4 · It replies in a threadUnder your message: the record link, the code, what it understood (rooms, price, place…), and what is still missing — the must-haves first.
5 · You answer in the thread"The price is 40 thousand", "three bathrooms not two", "it is in Salalab". The record is updated; the reply says what changed. Whatever you correct becomes a test case for the next version of the reader.
6 · Twenty rings the bellWhen the must-haves are all there and you have confirmed the price with the host, the record moves to the stage that means ready, and the thread says so.
7 · You say livelive 0005-01 in the thread. Only a person says this — never the machine.
8 · The home is on mkan.sdThe site gets the listing with its code, Twenty is updated to match, and the thread carries the link: mkan.sd/ar/listings/0005-01.

All eight boxes exist and are switched on (2026-08-25). The one Slack permission Box 2 needed was clicked the same day. What is still missing is the first real home — see Progress.

The parts

Six parts. Each one does a single thing:

PartWhat it does
Slack #mkanWhere you write, and where you are answered. Private channel C0BS2NZE2AY — already the mastering cockpit, so photos and homes share one room.
HermesThe ear. NousResearch's hermes-agent, the h lane, connected to Slack over Socket Mode from this Mac. Holds a one-line skill: hear a message, run the sweep, stay silent.
The script (mkan)The hands. scripts/crm/home-intake.ts — reads the message from Slack, extracts, validates, writes Twenty, writes the site on live, and posts every reply as the same kun bot. Also runs on a two-minute timer, so a home is caught even when Hermes is down.
ClaudeThe reader. claude -p on the Max plan turns your words into fields under a frozen, versioned prompt. No API key, no per-message cost.
Twenty homeThe record. The mkan workspace already carries everything this lane needs — ~100 fields, 147 homes today. Nothing new is added to the CRM.
The siteThe truth. mkan Prisma is where a listing lives; Twenty mirrors it. live writes the site first, then updates Twenty.

What you can write

You came back from a host's door with what he told you. Your own example, exactly as you would type it:

1. احمد ٠٩١٢٣١٠٢٠٥ الشقة الاولي غرفتين صالة حمام مطبخ

reads as: host أحمد, phone +249 91 231 0205 (Arabic digits are read as digits, 09… becomes +249…), home 1: two bedrooms, a hall, one bathroom, a kitchen. The reply asks for what is missing — the price, the neighbourhood or a map link — and offers a title.

Several homes of one host, in one message:

احمد ٠٩١٢٣١٠٢٠٥ — حي الثورة، قريب من المطار
1. الشقة الاولى غرفتين صالة حمام مطبخ ٣٠ الف الليلة
2. الشقة الثانية ثلاث غرف صالتين حمامين مطبخ مكيف ٤٥ الف
3. استوديو غرفة وحمام ١٥ الف

reads as three homes — 0005-01, 0005-02, 0005-03 — under the same host, all in Al-Thawra, priced 30,000 / 45,000 / 15,000 SDG a night. The third is a studio, which the CRM calls ROOMS.

In English, with a map link:

Fatima 0912 555 000 — villa in Salalab near the sea, 4 bedrooms 3 bathrooms,
AC, generator, 80k/night https://maps.app.goo.gl/…

Which job the words are

The channel is one desk with several jobs, and which job a message is gets decided in plain code before the reader is asked anything — because a correction read as fresh notes becomes a second copy of a flat that already exists.

You writeIt isWhat happens
a name, a phone, rooms, a pricenewa host record, an account, a code per unit
0004-02 and some wordsa change to that homeonly what your words change; the rest is left alone
0004-02 and nothing else (or just its link)a questionthe home's card comes back — what it holds, what is missing, whether it is live. No reader, no cost
0004-03 و 0004-02 السعر ٦٥a change to bothcodes match your words in the order you wrote them
live 0004-02 · انشرpublishwins over the code it carries
same 0004-02 · newa verdictanswers the "is this the same flat?" question, in its thread
a reply inside a home's threada change to that homeno code needed — the thread already knows
a fix with no code ("الحمامات بقت تلاتة")a change to which?the desk asks for the code rather than guessing
anything elsechattersilence

A code only counts when the CRM actually has it. That is what keeps 2026-08 in a date, or a typo, from taking over a message — an unknown code is not a code, and the words are read as notes instead.

What gets picked out

You writeIt becomesNeeded for live?
a phone number, in any digitshostPhone as +249…, plus the host recordyes — someone to call
a person's namehostNameexpected, not required
2 bedrooms · غرفتينbedroomsyes
3 bathrooms · حمامينbathroomsyes
villa · فيلا · شقة · استوديوpropertyTypeyes
30k/night · ٣٠ الف الليلةpriceNightSdgyes
a neighbourhood · a map linkzone (45 Port Sudan zones) · googleMapsUrlyes — one of the two
a title, a descriptiontitleAr, descriptionAr (English mirrored later)yes — drafted from the facts if you did not write them; you see the draft before live
wifi · صالة · مطبخ · مكيفhighlights · amenitiesnice
sleeps 4 · ٤ اشخاص · سريرينguestCapacity · bedsnice
numbered lines 1. 2. 3.one home per number, same host, codes in orderidentity
price confirmed · السعر مؤكد — agreed with the host in personpriceConfirmedByHostpart of the bell
anything else — generator, water tank, which floor, furnishedkept in the note on the record, never dropped

Words the CRM has no field for are not thrown away and not guessed at: they stay in your note, and if they keep appearing they earn a field or a few-shot in the next prompt version.

The level

A home is eligible when it has what the site itself needs to publish a listing, plus a phone. Photos are deliberately not required — many Port Sudan owners list without them and the card renders the branded placeholder — so they are a gap the reply mentions, not a wall.

Must-haveWhy
title · descriptionwhat the guest reads
price per night, SDGthe site refuses to publish without one
property type · bedrooms · bathroomsthe site's own publish floor
a place: zone, and a pin or a map linkthe listing needs a location row
host phonesomeone to reach when a guest books
price confirmed with the hostyour word, not the machine's — you agreed it at his door and say so in the thread

Nice to have — each one lifts the record's score: photos (three or more), beds, guests, five or more amenities, the English mirror of the copy.

The score is the CRM's existing dataCompletenessPct — ten core fields, computed by the same rubric the trust scorer uses (scripts/crm/trust-score.ts), so no two scripts ever disagree about a home's number. The bell is an existing stage, not a new word: when every must-have is present, the record moves VETTING → CLAIMED ("host confirmed pricing and rules"), and the thread says complete — say live 0005-01. Moving the card to CLAIMED by hand in Twenty rings the same bell.

Saying live

live 0005-01 in the thread, or live alone inside that home's thread. In order:

  1. The script checks the home is at CLAIMED with every must-have present — otherwise it refuses and names the gap.
  2. The site gets the listing first: the host account 0005 (created if new), the location row, and the listing with code 0005-01, published and out of draft. Nothing is fabricated — in particular the host's claim of the account stays empty until the host actually claims it, the way the funnel records it.
  3. Twenty is updated to match: publishState LIVE, pipelineStage LIVE, the site's id and URL, the publish time.
  4. Twenty's own webhook fires home.updated at the site, finds the row it just got, and re-confirms it — harmless, and it proves the mirror.
  5. The thread carries https://mkan.sd/ar/listings/0005-01.

Only a person says live. Setting publishState = LIVE in Twenty by hand only touches a listing that already exists on the site, so for a home born in Slack the word is the door.

The code

0005-01 is the host's account number and the unit's sequence — the same NNNN-NN the whole marketplace joins on (Listing.code on the site, listingId in Twenty; see Mastering). Accounts 0001–0004 are the four hand-verified hosts, 1001+ the scraped ones; a new host from Slack takes the next free manual number.

How the account is assigned, in order. The account number is a login slot, not a fact about the host0006 and the one shared password are what they type on mkan.sd, and the number has nothing to do with their phone. It is simply the next free one, read across both sides of the join at once: the account on every home and the mkanUsername on every host (a host can hold a number a day before their first home carries it, and reading one side only hands the same number out twice). 1001+ stays reserved for scraped hosts.

A known host keeps the account already written on their record; a host nobody knows gets a new host record, the next free number written onto it, and codes for their homes — whether or not a phone was written down. The phone stays an anchor for recognising a host and a must-have before live, so nothing reaches the site with nobody to call; it is no longer a gate on the numbering. A number that arrives later is written onto the host record it belongs to, unless it turns out to belong to a host who already has an account — then the thread says so and moves nothing, because welding two identities together on a guess is the mistake the same / new question exists to prevent.

The site account is provisioned when the first home goes live, and it is only the number: 0006 and the shared password are the whole of what a host is told. User.email is a required unique column, so it holds the number itself — the @mkan.org domain that used to sit behind it was removed on 2026-08-26 (69 accounts; pnpm accounts:simplify, reversible from its ledger). getUserByIdentifier answers to the number, to a host's own name, and still to the old address for anyone who types it from memory. username stays the host's name because that is what a guest reads under "Hosted by"; mkanUsername and Account provisioned are filled on the host record at that moment, and the CRM's mkan account field is legacy.

How the code is assigned. For a home born in Slack the CRM mints it the moment the host is known — the next unit under the account, in the order the units were written (the "first flat" in your message is not necessarily unit 01) — and the site keeps it at live (ensureListingCode sees a code and returns). A unit that looks like a home the host already has is not minted until you answer new. Known gap: the mint reads Twenty's codes but not yet the site's, so a host who publishes a flat through the wizard in between could take the same number; the site's unique index on code catches that at live, and the next step is to read both sides before minting.

It learns your way of writing

The reader is a prompt with a version number, and it improves the way the engine improves everything else — by measurement, not by drift:

  • Every message is kept: the words, what was read from them, which prompt version read them. On the record as a note, and in a local corpus.
  • Every correction is a test case. "Three bathrooms, not two" in the thread updates the record and lands in a corrections file. pnpm home:learn turns corrections into anonymised fixtures (phones and names replaced — the repository is public), drafts the next prompt version with your real phrasings as examples, and runs the whole set. Only a green set bumps the version, and a person commits it.
  • A wrong read is visible, never silent. The reply always shows what was understood; a message the reader cannot parse gets an honest "I could not read this" with what it saw, and goes into the corpus for the next version.
  • Hermes does not edit itself. Its skill for this channel lives in the mkan repository and is installed from there; the gateway's curator is not allowed to rewrite it.

Progress — the real trace (snapshot 2026-08-25)

Measured against the live CRM, gateway and Slack, not the docs:

PartState
Twenty home objectexists — 147 homes, ~100 fields, every stage and field this lane uses already defined; 33 LIVE, 26 publishReady, 129 still HUNTED
Twenty → site webhookregistered and firing on home.updated at mk.databayt.org/api/webhooks/twenty
Twenty browser UIbackmkan.databayt.org answers 200 again
Hermes gatewayrunning on this Mac, Slack and webhook platforms connected since 2026-08-23
#mkanexists (private, C0BS2NZE2AY), the bot kun is a member and posts there daily
The bot reading #mkanunblocked 2026-08-25groups:history + files:read added and the app reinstalled; the probe answers ok: true
The script (Phase 1)builthome:sweep / extract / intake / update / publish / status in mkan/scripts/crm/, 17 pure tests green
The earslive — Hermes bound to #mkan (every message wakes it, home skill, [SILENT]), launchd com.databayt.mkan-home-sweep every 2 min, one run lock
The first real homeproven 2026-08-25 — السند's two flats in one message: read right (2 bd · 1 ba · 7 beds · railway district · AC, washer, kitchen, TV, balcony; floor, iron, cooler, generator-by-agreement kept as notes), phone matched to the existing account-0004 host, matched one-to-one to 0004-02 / 0004-03 (live since 08-18), merged on same, beds corrected 4→7 from the scout's words, message attached as a note, both at 90%

Blocked on one small click, and only for the Hermes ear. The read scopes were added the same day and the timer ear has carried every home end to end since. Hermes itself has never woken: grep -c C0BS2NZE2AY ~/.hermes/logs/agent.log is still 0, because the app is not subscribed to the event that fires for a private channel. api.slack.com/apps → the kun app → Event Subscriptions → Subscribe to bot events → message.groups (and message.mpim, which the gateway warns about on every start) → Save → Reinstall. The steps, and why not to paste a whole manifest over the app, are in mkan/scripts/crm/hermes/SLACK-EVENTS.md. Until then the two-minute timer is the listener, which it has proven it can be — the cost is latency, not correctness.

Build ledger

CommitWhat landed
kun d1e9a34 · bf431afThis page — the lane drawn before any code; the mixed-script table made readable
mkan 7056c97The intake lane: home:sweep reads #mkan straight from Slack (cursor + thread ledger, idempotent by ts), claude -p under the frozen v1 prompt, vocabulary enforcement, host by phone, account + code minting, home + host + note, the trust rubric's own completeness, thread replies as the same bot, corrections file; the Hermes home skill + installer
mkan 5f795c3live 0005-01home:publish: site first (account, location — pin or zone centre said as such —, listing with its code), then Twenty mirrors LIVE; run lock between the two ears; FORCE_SEED carried by both installers
mkan 334a7a0The site's Twenty webhook mints the code when a home is flipped LIVE from the CRM
mkan 283d7e4 · 10e7fd9 · 6374406Learned from the first real message: duplicates matched one-to-one and asked (same 0004-02 0004-03 merges, new creates); the host record's own account is the truth; no phone → nothing minted; the scout's numbers win on a merge and land in the corrections file; place names (a building, a market) are address words, not leftovers; a home already on the site is told so instead of asked to go live; home:answer for decisions taken elsewhere

Next

The first live of a home born here (the first message described two flats that were already on the site). Then: read the site's codes before minting; a host 0004 hint in the words to force a host match when the phone is new. Phase 2 — the bell from Twenty for edits made in the CRM's own UI (a workflow on pipelineStage → CLAIMED posting to Hermes' webhook route, the same shape the hogwarts funnel already runs). Phase 3 — photos posted in the thread, re-hosted on the CDN and attached to the record so mastering picks them up by itself. Phase 4 — home:learn, the gated prompt-version loop (the corrections file is already being written).

Traps, measured

Each of these cost one real round trip on 2026-08-25; none is theoretical.

  • A host's second flat is not a duplicate of the first. The first dedup rule (same phone, same rooms, same type) flagged both of السند's flats against the same record and created nothing — with no way to answer. Duplicates are now matched one-to-one by likeness and the thread asks: same … or new. The scout decides; the machine never merges on its own.
  • Stale numbers in the CRM. 0004-02 and 0004-03 carried beds = 4; the scout wrote "سبعة سرير". On same, the scout's numbers win — they were at the door — and every changed number is written to corrections.jsonl, which is how the CRM got corrected instead of the message being bent to fit it.
  • "مشغل دبي" is an address, not a leftover. The reader filed the building's name under leftover; the prompt now says building, operator, market and landmark names are place words.
  • Already live is not "say live". The merged flats had been on mkan.sd for a week and the reply still offered live. Replies and home:publish now read the publish state first.
  • An ear that is connected can still be deaf. Scopes let the bot read; only the message.groups event makes Socket Mode deliver private-channel messages. The timer ear was designed for exactly this gap and carried the whole first run.
  • depth=0 hides relations. Twenty's REST answers noteTargets: [] at the default depth; the notes were attached all along. Check with depth=1 before calling something missing.

Decisions on record

  • No wake word. Hermes is woken by every message in #mkan; the script decides what each one is, and stays silent on the rest.
  • Hermes hears; the script understands; the same bot answers. The gateway model never touches the words, and every reply is posted by the script — so the lane also works when the gateway is down.
  • The reader is claude -p on the Max plan — no API key, no per-message cost — under a frozen, versioned prompt validated against the CRM's own field list.
  • Target object is homes, where the webhook, the trust rubric, mastering and the sync loop already live. A home born here does not appear on the Port Sudan board until mirrored.
  • No new vocabulary. VETTING while gathering, CLAIMED when eligible, LIVE after the yes; source = FIELD_SCOUT — because that is what this is, a scout who met the host; Twenty's option lists stay untouched (they are append-only).
  • The level is the site's publish floor plus a phone; photos are a gap, not a wall.
  • A person says live. Never the machine.
  • State truth is mkan Prisma; Twenty mirrors. The CRM mints the code when the host is known; the site keeps it.
  • The account number is a sequence, not a fact. 0006 is a login slot with one shared password, minted in order from the whole CRM at once and written onto both the host and every home. It is unrelated to the phone: a host filed without a number still gets an account and codes, and still cannot go live until someone can be called. (Reversed 2026-08-26, by Abdout, from the 08-25 "no phone → nothing minted".)
  • Dedup before create. A thread reply is always an update; a new message that matches an existing host phone and unit is flagged SUSPECTED and asked about, not duplicated.
  • Improvement is a gated loop. Corrections become tests; a prompt version ships only green.
  • Mac-bound by nature. Twenty and Hermes live on this laptop; the intake desk is open when the lid is. A message sent while it sleeps waits — the sweep cursor picks it up on wake.
  • CRM — the Twenty fork, workspaces, webhooks, and why deletes are hard
  • Mastering — the sibling loop in the same channel: photos in, better photos out
  • Funnel — mkan's activation ladder and the retention loop after LIVE
  • Scrape & Leads — the other way homes reach the CRM
  • Hermes — the gateway lane, Slack wiring, and the relay doctrine
  • Slack — the workspace, channels, and the three different Slack pipes
  • Mkan social — the brand this lane feeds