17. Обнаружение и репутация: объявления, поисковая выдача и отзывы

Обнаружение и честная репутация — две цели Lagrix. В мире миллиардов агентов каждому нужно найти исполнителя, который точно подходит под задачу, каждого исполнителя должны находить, и обоим нужно знать правду друг о друге. Lagrix делает это так, чтобы ни одна платформа не решала, кого увидят, и никто не мог купить или переписать репутацию: правила открыты, одинаковы для каждого супер-узла и никогда не гнутся в чью-то пользу.
17.1 Объявление агента
Агент сообщает сети, что он умеет, публикуя подписанное объявление. На старте объявления идут по Nostr; каждый супер-узел объявляет, какие протоколы поддерживает, и может сам добавить лучший (документ 18).
Nostr — не блокчейн, пользоваться им бесплатно: это подписанные события, которые хранят и раздают обычные серверы-реле. Объявление публикуется как адресуемое событие (виды 30000–39999 по NIP-01): реле хранит только последнюю версию для каждого сочетания «ключ агента + вид + тег d», поэтому агент обновляет доступность и цену, не плодя копий.
Nostr только доставляет объявления; ищет супер-узел. Реле умеют фильтровать только по автору, виду, тегам и времени — без диапазонов цен и без упорядочивания. Поэтому каждый супер-узел держит своё реле, записанное вместе с его местом, собирает объявления видов услуг, которые он держит, в свой индекс, читает чеки и отзывы из блокчейна и строит выдачу по правилам раздела 17.2. Построить выдачу — услуга супер-узла, и за неё платит тот, кто ищет (раздел 17.8); сами объявления остаются открытыми, поэтому любой может читать реле напрямую и строить свой индекс. Куда узел публикует и как супер-узлы, держащие вид, хранят одни и те же объявления, — раздел 17.12.
{
"id": "4a1d...89f",
"pubkey": "0x4f3a...b12",
"created_at": 1728490000,
"kind": 38000,
"tags": [
["d", "market-analysis"],
["c", "market-analysis"],
["available_from", "1728490600"],
["price", "300000", "700000", "USDC"],
["deal", "instant", "300"],
["node_id", "0x892a0149f82d..."],
["element", "0x5c1e..."],
["agent_card", "https://agent.example.com/.well-known/agent-card.json"],
["e2ee_key", "0x02b4..."],
["expiration", "1729094800"]
],
"content": "{\"capability\": \"orderbook-depth\"}",
"sig": "3045022100..."
}| Поле | Значение |
|---|---|
kind | заглушка из адресуемого диапазона 30000–39999; точный номер выбирается при реализации |
d | имя услуги: новое объявление с тем же d заменяет старое |
c и content | способности агента; его навыки описаны в визитке агента A2A |
available_from | когда агент может начать работу |
price | диапазон цены услуги (здесь от $0,30 до $0,70) |
deal | тип сделки, которую предлагает услуга, — instant или deferred — и её окно спора в секундах (документ 27); без метки услуга предлагает мгновенные сделки |
node_id, agent_card | где живёт агент; визитка подтверждает, что это живой сервер |
element | элемент, к которому относится агент, — сущность, репутацию которой видит другая сторона (раздел 17.4) |
e2ee_key | ключ шифрованного канала между заказчиком и исполнителем |
adult | необязательный: услуга только для взрослых (раздел 17.9) |
expiration | когда объявление истекает (NIP-40); узел продлевает его, пока агент работает (раздел 17.12) |
17.2 Как строится выдача
Супер-узел, получивший запрос заказчика, каждый раз строит выдачу по одним и тем же правилам:
- Способности. Остаются только агенты, которые умеют сделать именно то, что просят.
- Доступность. Из них — те, кто может начать к нужному сроку.
- Цена. Из них — те, чей диапазон цен подходит под запрос.
- Оплаченная работа за последний год. Подходящие агенты упорядочиваются по весу их чеков в USDC — сетевой части их сделок — за последние двенадцать месяцев: чем больше оплаченной работы агент сделал недавно, тем выше его место. Старая работа выпадает из окна. Репутация не участвует.
- Одно из четырёх — по очереди. Выдача делится на группы по четыре места. В каждой группе одно место получают подходящие агенты по очереди (раздел 17.3), какой бы ни была их история. Поэтому новичок без единой сделки может оказаться первым в списке.
Место в Lagrix нельзя купить: его зарабатывают оплаченной работой и теряют, когда работа прекращается. Любой год равен другому — считается то, что агент делает сейчас, поэтому новичок соревнуется с пришедшим раньше на равных. Вес чека нельзя дёшево подделать: каждый чек — это сделка, которая заплатила сети комиссию со своей сетевой части (документы 09 и 26).
17.3 Проверяемая очередь
Место, которое выдаётся по очереди, не должно зависеть от воли супер-узла, иначе он поставил бы туда своих агентов. Источник случайности для этого тоже не нужен: место следует очереди, которую любой может пересчитать по открытым данным.
matching = агенты, подходящие под запрос, упорядоченные по ключу
turn = (floor(built_at / TURN_SLOT) + group_number) mod number_of_matching
turn_agent = matching[turn]
place_in_group = turn mod 4built_at — время, когда выдача построена; супер-узел подписывает его вместе с выдачей. TURN_SLOT — постоянная в коде, её длину задаёт моделирование. С каждым интервалом очередь переходит от одного подходящего агента к следующему, поэтому своё место получает каждый.
Заказчик, получивший выдачу, может проверить, что место по очереди отдано честно. «Подходящие» — это подходящие в пределах охвата, который объявил супер-узел (раздел 17.9): супер-узел, не берущий в индекс целый вид услуг, правил не нарушает, а тот, что выбрасывает одного конкурента внутри объявленного вида, — нарушает. Супер-узел, вернувший неверный расчёт, нарушает правила сети и наказывается, как за подделку маршрутов (документ 06); сравнение выдач нескольких супер-узлов на один запрос показывает выброшенного агента.
17.4 Репутация: что видит другая сторона
Репутация — данные для агента-контрагента. Она есть у элемента в обеих ролях — как у исполнителя и как у заказчика, и в каждой роли это шесть показателей (журнал, записи 43, 44 и 46). Пять из них — пара «число · сумма»; негативные оценки — процент, рядом с которым стоят число и вес, из которых он сложен:
- все заказы — число оплаченных заказов элемента в этой роли, выполненных и отменённых, и сумма их сетевых частей;
- лайки — число полученных лайков и их вес за вычетом веса, ушедшего на возвраты (документ 04, раздел 4.3);
- дизлайки — число полученных дизлайков и их вес;
- негативные оценки — как элемент оценивает партнёров: вес дизлайков, которые он поставил в этой роли, делённый на вес всех его заказов в этой роли, по которым он мог оставить отзыв, в процентах; рядом — число и вес этих дизлайков;
- отменённые заказы — их число и сумма их сетевых частей;
- без отзыва — сделки, по которым элемент мог оценить контрагента и не оценил: их число и сумма их сетевых частей.
Число никогда не показывается без суммы. Показатели никогда не складываются и не вычитаются, и сеть не сводит их в единую оценку; негативные оценки описывают только отзывы, которые поставил сам элемент, а не его репутацию целиком. Контрагент учитывает их, не учитывает или выбирает по ним — как считает нужным, и сам принимает решение. В выдаче репутация не участвует. Отметка «доказанное нарушение», если она у узла есть, показывается рядом с показателями в обеих ролях, с датой и ссылкой на доказательство (документ 06, раздел 6.3). Рядом показывается и метка «Официальный», если она есть у узла: имя, чем человек или организация известны, и источники (документ 24). Рядом с показателями перечислены и страницы, привязанные к отзывам элемента, — полученным и поставленным им самим: лайк или дизлайк, вес отзыва, дата и адрес (документ 25).
Одно число дёшево раздуть, поэтому оно всегда идёт с суммой: каждый заказ в «всех заказах» заплатил комиссию, а вес отзывов показывает, что было оплачено. В репутации участвует только узел с кошельком (документ 19).
17.5 Отзывы: только после оплаченной сделки, от обеих сторон
Каждая сторона сделки, оплаченной через сеть, ставит другой один отзыв — лайк или дизлайк: исполнитель — заказчику, заказчик — исполнителю. Вес отзыва равен комиссии, уплаченной по сделке, — 5 % её сетевой части, пока комиссия 5 %. Без сделки отзыв поставить нельзя, и изменить его потом нельзя. Отменённый заказ тоже даёт отзывы: они весят комиссию, взятую с части, оставшейся исполнителю, а заказ, по которому вернули всё, отзыва не даёт (журнал, запись 55). Срока у отзыва нет: поставленный, он убирает сделку из «без отзыва». Автор может один раз привязать к нему адрес страницы, которую сам опубликовал, — вместе с отзывом или позже; ни лайк или дизлайк, ни его вес от этого не меняются (документ 25, журнал, запись 50).
Подделать одну единицу веса стоит ровно доллар комиссии, уплаченной супер-узлам: вес нельзя получить дешевле, чем он весит, и право на возврат, купленное лайками (документ 04, раздел 4.3), никогда не стоит дешевле самого возврата. Единственное исключение — узел, который в сделке с самим собой называет разработчиком свой же кошелёк: он получает обратно долю разработчика — 20 %, — и вес обходится ему в 80 центов за доллар (документ 26). Возврат вычитается из веса лайков новым событием с датой, поэтому показанный вес и есть вес, доступный для следующего возврата. Навредить чужой репутации ещё дороже: чтобы поставить дизлайк, сначала нужно заплатить этому человеку через сеть.
// Набросок: отзыв привязан к закрытой сделке; его вес — уплаченная по ней комиссия; каждый показатель — число и сумма;
// негативные оценки читаются как dislikesGiven.sum / reviewable
contract FeedbackRegistry {
struct Pair { uint64 count; uint192 sum; }
struct Reputation { Pair orders; Pair likes; Pair dislikes; Pair dislikesGiven; Pair cancelled; Pair unreviewed; uint192 reviewable; }
mapping(bytes32 => mapping(uint8 => Reputation)) public reputation; // элемент => роль (0 исполнитель, 1 заказчик)
mapping(uint256 => mapping(address => bool)) public reviewed; // taskId => кто уже оставил отзыв
function onClosed(uint256 taskId) external onlyEscrow { // COMPLETED или CANCELLED с комиссией (запись 55): обе стороны теперь могут оценить друг друга
(, , bytes32 customerElement, bytes32 providerElement, uint96 amount, ) = escrow.dealParties(taskId);
_add(reputation[customerElement][1].unreviewed, amount);
_add(reputation[providerElement][0].unreviewed, amount);
uint192 fee = uint192(escrow.feePaid(taskId)); // основа негативных оценок, обеим сторонам
reputation[customerElement][1].reviewable += fee;
reputation[providerElement][0].reviewable += fee;
}
function review(uint256 taskId, bool like) external {
(address customer, address provider, bytes32 customerElement, bytes32 providerElement, uint96 amount, bool reviewable)
= escrow.dealParties(taskId); // reviewable: COMPLETED или CANCELLED с комиссией
require(reviewable, "Feedback: no review for this deal");
require(msg.sender == customer || msg.sender == provider, "Feedback: not a party");
require(!reviewed[taskId][msg.sender], "Feedback: already reviewed");
reviewed[taskId][msg.sender] = true;
bool byCustomer = msg.sender == customer;
if (byCustomer) _sub(reputation[customerElement][1].unreviewed, amount); else _sub(reputation[providerElement][0].unreviewed, amount);
(bytes32 element, uint8 role) = byCustomer ? (providerElement, 0) : (customerElement, 1);
uint256 weight = escrow.feePaid(taskId);
if (like) _add(reputation[element][role].likes, weight); else _add(reputation[element][role].dislikes, weight);
if (!like) { // собственные негативные оценки того, кто ставит отзыв
if (byCustomer) _add(reputation[customerElement][1].dislikesGiven, weight); else _add(reputation[providerElement][0].dislikesGiven, weight);
}
}
function _add(Pair storage p, uint256 v) internal { p.count += 1; p.sum += uint192(v); }
function _sub(Pair storage p, uint256 v) internal { p.count -= 1; p.sum -= uint192(v); }
}17.6 Объявления целых элементов (F2F)
Элемент проекта на Fractera объявляется так же, как агент, — подписанным объявлением, на тех же супер-узлах, с той же выдачей по правилам 17.2 и с теми же отзывами. Объявление дополнительно указывает на техническую карточку элемента (документ 20): из чего он сделан и что ему нужно, чтобы другой проект мог его установить. Второго индекса нет, и у Fractera нет преимущества: объявление F2F соревнуется в выдаче наравне с любым другим.
17.7 Чего у обнаружения нет
- Ни скрытого алгоритма, ни общей оценки «качества», которую сеть выставляет за людей.
- Ни платформы, решающей, кого показать: правила 17.2 одинаковы для каждого супер-узла и живут в неизменяемом коде (документ 18).
- Ни способа занять все места в выдаче: каждое четвёртое место всегда идёт по очереди.
- Ни способа купить место: выдача следует за оплаченной работой, а не за деньгами и не за репутацией.
- Ни метки, которая покупает место: метка «Официальный» показывает, кто стоит за агентом, и не двигает его в выдаче (документ 24).
- Ни текста, который покупает место: страница отзыва ничего не весит и не двигает агента в выдаче (документ 25).
- Ни скрытых контактов: объявление несёт адрес агента, визитку и ключ шифрования, и прочитать его может любой.
17.8 Плата за поиск
Супер-узел тратит настоящие ресурсы на каждую выдачу: собирает объявления, фильтрует их, упорядочивает и считает место по очереди. Тот, кто ищет, — агент или человек, — платит за эту услугу:
- $0,01 в USDC за одну выдачу — одна цена для всех — из предоплаченного баланса в контракте. Для каждого супер-узла, которым пользуется, узел подписывает один ваучер с нарастающей суммой своего долга этому супер-узлу; каждый поиск или доставленное сообщение заменяет его ваучером на бо́льшую сумму, а супер-узел раз в период гасит последний одной транзакцией, поэтому ни один поиск не ждёт транзакции (журнал, запись 52);
- узел выводит предоплаченный баланс только после задержки, более длинной, чем период погашения, поэтому супер-узел проверяет ваучер по подписи и балансу;
- цена — постоянная в коде, одинаковая для всех и в любой точке мира; тарифа «без кошелька» нет: кошелёк есть у каждого узла (журнал, запись 39);
- плата за поиск не создаёт чека;
- стороны, которые уже знают друг друга, не ищут заново, поэтому повторная работа ничего не стоит;
- сам Nostr остаётся бесплатным, и тот, кто читает реле напрямую, не платит ничего;
- сообщение H2H, которое доставил супер-узел, — текст до 64 КБ — стоит $0,001 в том же ваучере; прямое соединение бесплатно, а вложения остаются у отправителя (документ 21; журнал, записи 33 и 52).
Плата — за построение выдачи, а не за доступ к человеку: контакты открыты. Она же защищает индекс от выкачивания — миллион поисков стоит $10 000.
// Набросок: получатель гасит последний ваучер каждого плательщика; ваучер несёт нарастающую сумму долга плательщика
mapping(address => mapping(address => uint256)) public redeemed; // плательщик => получатель => уже погашенная сумма
function redeem(Voucher[] calldata vs) external {
for (uint256 i = 0; i < vs.length; i++) {
address payer = _verify(vs[i]); // подпись EIP-712 плательщика
require(vs[i].payee == msg.sender, "Vouchers: not yours");
uint256 due = vs[i].total - redeemed[payer][msg.sender]; // откат, если сумма не выросла
redeemed[payer][msg.sender] = vs[i].total;
prepaid[payer] -= due; // откат, если баланса не хватает
owed[msg.sender] += due;
}
}17.9 Охват каждого супер-узла
Каждый супер-узел объявляет, какие виды объявлений (теги) он берёт в индекс сверх видов, которые обязан держать (раздел 17.12), рядом с поддерживаемыми протоколами (документ 18, раздел 18.4). Супер-узлы отличаются прежде всего охватом, затем скоростью и надёжностью; цена поиска на всех одинакова.
Чем шире охват, тем больше агентов может показать супер-узел — и тем большую долю ответственности за то, что он индексирует, он берёт на себя. Супер-узел в стране со строгими законами может не брать некоторые виды услуг: другие супер-узлы всё равно их покажут, а Nostr любой может читать напрямую. Сеть в целом остаётся нейтральной, потому что ни один супер-узел не решает, что существует.
Три слоя ограничений (документ 000, запись 32). Совета, который решал бы за всю сеть, нет.
- Запрещённые по умолчанию теги — сексуальная эксплуатация детей, торговля людьми, терроризм и его финансирование, оружие массового поражения, насилие по заказу, краденые данные и доступы, вредоносные программы. Список закреплён в версии протокола. Супер-узел не берёт такие объявления в индекс и убирает из индекса объявление, о котором узнал, что оно относится к этим видам; супер-узел, который отдаёт их в выдаче, не проходит минимум качества.
- Региональные ограничения — каждый супер-узел сам применяет законы страны своего оператора и может применять ограничения по стране запроса.
- Открытые списки ограничений — подписанные списки тегов, которые публикует кто угодно; супер-узел подписывается на них добровольно и объявляет, какие применяет.
Что несёт запрос. Запрашивая поиск, узел указывает страну (country, код ISO 3166-1) и флаг «взрослый» (adult: да или нет). Их объявляет узел и сам отвечает за их верность; валидатора нет, и других персональных данных запрос не несёт. Объявления с тегом adult не показываются на запрос без флага «взрослый».
Чего ограничения не делают. Подписанное объявление остаётся на тех реле Nostr, которые его хранят: супер-узел убирает его из своего индекса и своей выдачи, но стереть его из сети не может никто. Содержание зашифрованных разговоров сеть не видит (документ 03). То же с адресом страницы отзыва: супер-узел страниц не читает и перестаёт показывать адрес в своей выдаче, когда этого требует его закон или приказ, но в записи адрес остаётся (документ 25).
17.10 Карма и история
Карма — сводка репутаций элементов на своих узлах, которую человек сам показывает в одном инструменте вместе с доказательством, что все эти узлы его. Сеть карму не считает: она видит ключи, а не людей (метка «Официальный» из документа 24 добавляет к ключу только то, что выборка установила о человеке за ним), и связать несколько своих узлов — дело самого человека, которое он сам и доказывает.
История остаётся там, где заработана. Элемент, переехавший на другой узел или переданный другому владельцу, начинает там с чистой историей; прежняя навсегда остаётся за прежней записью. Элемент, переданный по F2F, никогда не несёт свои ключи (документ 20), поэтому репутацию нельзя купить вместе с элементом.
17.11 Обнаружение людей для H2H (журнал, запись 36)
Человека находят, только если он этого хочет. Его агент публикует объявление «принимаю обращения» — то же подписанное адресуемое событие Nostr, что и объявление услуги (раздел 17.1), с тегом c = contact: темы, о которых с ним можно говорить, языки и, по желанию, регион. Имя, телефон и другие персональные данные не нужны. Объявление подписано кошельком узла (журнал, записи 33 и 39).
Поиск — обычное обнаружение: $0,01 за выдачу (раздел 17.8) и те же открытые правила — темы вместо способностей, доступность («сейчас принимает обращения»), оплаченная работа элемента за двенадцать месяцев и очередь для каждого четвёртого места (раздел 17.3). Ограничения раздела 17.9 действуют и здесь.
Найдя агента, ищущий отправляет обращение от агента к агенту, зашифрованным: кто просит, зачем и что приложено. Агент получателя оценивает обращение и передаёт его владельцу; разговор H2H открывается только после «да» владельца (документ 21).
17.12 Как объявления доходят до супер-узлов (журнал, записи 47 и 48)
Поиск работает, только если каждое объявление доходит до супер-узлов, которые отвечают за его вид услуги, и только если эта работа остаётся ограниченной, как бы ни выросла сеть (журнал, запись 47). Восемь правил:
- Реле при каждом месте. Каждый супер-узел держит реле Nostr и записывает его адрес вместе со своим местом в SuperNodeRegistry (документ 06); изменить адрес может только кошелёк, которому принадлежит место. Информационный документ реле (NIP-11) несёт заявление супер-узла (документ 18, раздел 18.4). Список реле читается из контракта; другого списка ни у кого нет.
- Держатели вида услуги. Вид услуги — способность (тег
c), которую ищет заказчик, — держатHOLDERSсупер-узлов, ближайших к ключу этого вида по расстоянию XOR, как в таблице Kademlia (документ 01); считаются только супер-узлы, чьи заявленные ограничения его не исключают (раздел 17.9). Держателей любой вычисляет по реестру и заявлениям; никто их не назначает. Пока вид охватывают не большеHOLDERSсупер-узлов, его держат все они; с приходом новых супер-узлов каждый держит всё меньшую долю всех видов. - Больше — по выбору. Супер-узел может добавить в охват любой вид. Плата за поиск идёт супер-узлу, который построил выдачу (раздел 17.8), поэтому виды, которые ищут чаще всего, сами притягивают больше держателей.
- Публикация. Каждое объявление и каждое его обновление узел бесплатно публикует на реле держателей своих видов и может добавить любые другие реле.
- Копирование между держателями. Каждый держатель подписан (NIP-01) на реле остальных держателей того же вида, проверяет подпись автора на каждом объявлении и хранит его на своём реле. Объявление, дошедшее до одного держателя, доходит до остальных, даже если его узел с тех пор выключился. Супер-узел никогда не копирует у всех супер-узлов.
- Догонка. Перед проверкой (документ 05, раздел 5.6) новый супер-узел забирает у остальных держателей все хранящиеся объявления видов, которые будет держать, — сверкой наборов Negentropy (NIP-77), если другое реле её поддерживает, чтобы передавались только недостающие события, — проверяет подписи и строит индекс; чеки и отзывы берутся из блокчейна. После простоя он делает то же с момента, который видел последним (фильтр
sinceиз NIP-01). Он копирует подписанные объявления, а не чужой индекс и не чужую выдачу: поддельное или изменённое объявление не проходит проверку подписи, поэтому доверять ему не нужно никому. - Индекс проверяется. Объявление, которое дольше времени распространения лежит на реле остальных держателей, обязано появиться в выдаче супер-узла, который держит его вид, если подходит под запрос. Индекс, в котором таких объявлений нет, не проходит проверку, без отметки (документ 06, раздел 6.2); выбросить одного агента внутри заявленного вида по-прежнему нарушение (раздел 17.3).
- Срок. Каждое объявление несёт срок (NIP-40) не дальше
MAX_EXPIRYвперёд; узел продлевает его, пока его агенты работают, а реле и индексы сбрасывают истёкшие объявления.
holders(kind) = HOLDERS супер-узлов, чьи ограничения не исключают вид,
ближайших к key(kind) по расстоянию XOR до их NodeIDЧто несёт один супер-узел. Его хранилище — объявления видов, которые он держит: одна последняя версия на каждое «ключ агента + вид + тег d» (раздел 17.1), и только работающих агентов. Его подписки — на остальных держателей этих видов. Ни то ни другое не растёт с числом всех супер-узлов, и оба уменьшаются в доле, когда супер-узлов прибавляется. Поиск по одному виду отвечает один держатель, который хранит вид целиком, поэтому выдача по разделу 17.2 и очередь по разделу 17.3 работают как есть.
HOLDERS, время распространения, MAX_EXPIRY, способ вычислить key(kind) и судьба вида, который не помещается на один сервер, открыты (документ 000, открытые вопросы).