NIJIKAI
A Tokyo Settai Planner for the Person Who Books the Evening
夜の手配を担う人のための、東京の接待プランナー
Overview
概要二次会 (nijikai) is the second party of the night — the bar or the karaoke box after dinner, where the actual conversation happens. The app is named for it because that leg is the one every restaurant-finder ignores, and the one a client evening turns on.
二次会は、夕食の後に行くバーやカラオケ、本当の会話が始まる夜の二軒目です。アプリの名前をそこから取ったのは、どのレストラン検索も無視しているのがこの二軒目であり、接待の夜の成否を分けるのもここだからです。
Restaurant sites answer 'where is good?'. A secretary booking a client dinner is answering a different question entirely: which room will hold six people, has a door on it, can feed a guest who does not eat pork, takes a corporate card, and is bookable eleven days from now. Nijikai answers that one, and treats a superb restaurant that fails a hard requirement as dropped rather than flagged.
グルメサイトが答えるのは「どこがおいしいか」です。接待の食事を手配する秘書が答えようとしているのは、まったく別の問いです。6人が入れて、扉のある個室で、豚肉を食べないお客様に対応でき、法人カードが使えて、11日後に予約できる店はどこか。Nijikaiはこちらの問いに答えます。どれほど素晴らしい店でも、必須条件を満たさなければ、注意書きをつけて残すのではなく候補から外します。
How it was built
開発It shares its architecture with Sakurako, and for the same reason. A brief becomes roughly fourteen deterministic Exa queries planned in Rust, whose results go to a single toolless model instance that reads them once and writes the shortlist; a second instance turns the picked rooms into a runsheet. Searching is written the way the job is actually done — the word that matters is 個室 (private room), not 'best restaurant'; a bonenkai is searched in Japanese (忘年会) because the course-and-nomihoudai banquet plans never surface in English; and a dietary constraint gets its own query, because a general restaurant search will never turn up a halal kitchen.
設計はSakurakoと共通で、理由も同じです。依頼内容はRustが組み立てる約14件の決まったExa検索になり、その結果を、ツールを持たない一つのモデルが一度だけ読んで候補リストを書きます。もう一つのモデルが、選ばれた店から当日の進行表を作ります。検索は実際の手配のやり方に合わせています。重要な言葉は「おすすめのレストラン」ではなく「個室」です。忘年会は日本語で検索します。コースと飲み放題の宴会プランは英語では出てこないからです。食事制限には専用の検索を用意しています。一般的なレストラン検索ではハラールの店は見つからないからです。
What it does
機能Occasion, party size, guest seniority, languages in the room, dietary constraints, how much they will drink, and a 1–5 register dial. The register does more work than the budget: two steps too loose reads as a slight, two steps too tight reads as trying too hard.
目的、人数、お客様の役職、その場で使う言語、食事制限、お酒の量、そして1〜5段階の格式のダイヤル。格式は予算以上に重要です。2段階くだけすぎれば軽んじていると受け取られ、2段階堅すぎれば力みすぎに見えます。
Each result carries per-head price, private-room status, seat count, booking lead time, card acceptance and last order — plus a risks list saying how it could embarrass the host. Cash only. No private room on a Friday. Fifteen minutes from the station. A venue with three honest risks listed is more useful than one described as perfect.
各候補には、1人あたりの価格、個室の有無、席数、予約に必要な日数、カードの可否、ラストオーダーに加えて、主催者に恥をかかせかねない点を挙げたリスク一覧がつきます。現金のみ。金曜は個室なし。駅から15分。正直なリスクが3つ書かれた店のほうが、完璧と書かれた店より役に立ちます。
If a price or a phone number was not on a page, the field is empty and the gap is named. A secretary can settle an unknown with one phone call; they cannot un-ring a number the model invented.
価格や電話番号がページになければ、その欄は空欄のまま、情報が欠けていることを明示します。秘書なら不明点は電話一本で確かめられますが、モデルが作り上げた番号は取り消せません。
Pick a room per leg and it comes back timed and costed, carrying the things that are on no restaurant page: the kamiza seating plan for this party and this room, how the bill gets settled out of the guest's sight, the last trains per line — because those end the night, not the venue's closing time — whether a temiyage is expected, and what to do when the nijikai turns out to be full. Exportable as a PDF to hand to whoever is hosting.
一軒ごとに店を選ぶと、時間と費用つきの進行表が返ってきます。どのお店のページにも載っていないことも含まれています。この人数とこの部屋での上座・下座の配置、お客様の目に触れずに会計を済ませる方法、路線ごとの終電(夜を終わらせるのは店の閉店時間ではなく終電だからです)、手土産が必要かどうか、二次会の店が満席だったときの対応。PDFで書き出して主催者に渡せます。
privateRoom, englishSupport and corporateCardOk are three-state on purpose. false means the model read that there is not one; null means nobody knows yet. Collapsing them turns 'unconfirmed' into 'confirmed absent', which is how a party ends up seated in the middle of a dining room.
個室、英語対応、法人カードは、あえて3つの状態にしています。falseは「ないと書かれていた」、nullは「まだ誰も知らない」という意味です。この2つをまとめると「未確認」が「ないことを確認済み」に変わり、接待の一行がホールの真ん中の席に座ることになります。
There is no Nijikai back end. The brief goes to the model as a prompt when you run a search, that instance queries Exa and fetches pages, map tiles come from a CDN, and everything else — brief, shortlist and runsheets — stays in a single JSON file on your machine that About will show you the path to.
Nijikaiにバックエンドはありません。検索を実行すると、依頼内容はプロンプトとしてモデルに送られ、そのモデルがExaで検索してページを取得します。地図のタイルはCDNから読み込みます。それ以外の依頼書、候補リスト、進行表はすべて、手元のマシンの一つのJSONファイルに保存され、その場所は「このアプリについて」で確認できます。
Under the hood
技術の詳細One toolless pass instead of a fan-out分散させず、ツールなしの一回の処理で
The shortlisting instance gets one flat block of results and no tools at all. The earlier design fanned out to a dozen headless instances each running their own search loop, and each paid roughly 21k tokens of harness overhead before doing any work, re-sending every fetched page on every later turn. Collapsing it took a run from about $2.15 to $0.20.
候補を絞り込むインスタンスには、平らに並べた検索結果を渡し、ツールは一切与えません。以前の設計では十数個のヘッドレスのインスタンスがそれぞれ検索を繰り返し、各インスタンスが作業前に約2万1千トークンの準備コストを払い、取得したページを毎ターン送り直していました。一つにまとめたことで、1回の実行は約2.15ドルから0.20ドルになりました。
Enforcing 'never invent a URL'「URLを捏造しない」を強制する
Every candidate URL is already in the prompt, so strip_unknown_urls deletes any booking or info link the model did not actually get from the results. The instruction is not trusted; the output is filtered against the input.
候補のURLはすべてプロンプトに含まれているため、strip_unknown_urlsが、モデルが検索結果から実際に得ていない予約リンクや情報リンクを削除します。指示を信じるのではなく、出力を入力と照らし合わせて絞り込みます。
Three-state, because absence is not denial「ない」と「わからない」は違うから3状態
privateRoom, englishSupport and corporateCardOk distinguish 'read that there isn't one' from 'nobody knows yet'. Collapsing the two turns unconfirmed into confirmed-absent, which is how a client party ends up seated in the middle of a dining room.
個室、英語対応、法人カードは、「ないと書かれていた」と「まだ誰も知らない」を区別します。この2つをまとめると、未確認が「ないことを確認済み」に変わり、接待の一行がホールの真ん中の席に座ることになります。
Sizing prompts for scraped text取得したテキストに合わせたプロンプトの見積もり
Web text tokenises at roughly 2 characters per token, not 4. Sizing a prompt by chars/4 underestimates by 2× and trips the spend cap — one of several numbers here that only reveal themselves in production.
Webのテキストは1トークンあたり4文字ではなく約2文字です。文字数÷4で見積もると2倍少なく見積もってしまい、費用の上限に引っかかります。本番で動かして初めてわかった数字の一つです。
Stack
技術構成Tauri 2 with a Rust core. The shortlisting pass is given no tools at all: it receives one flat block of results and reads it once. That is deliberate — the earlier design fanned out to a dozen headless instances that each ran their own search loop, and each paid roughly 21k tokens of harness overhead before doing any work, re-sending every fetched page on every subsequent turn. Collapsing it to one toolless instance took a run from about $2.15 to $0.20. Model output is distrusted by construction: every candidate URL is already in the prompt, so strip_unknown_urls deletes any bookingUrl or infoUrl the model did not actually get from the results — 'never invent a URL' is enforced rather than requested. Several constraints cost real time to find and are load-bearing: Tauri runs synchronous commands on the main thread, so anything doing network or process I/O goes through an off-thread helper or the window freezes; scraped web text tokenises at roughly 2 chars/token rather than 4, so sizing a prompt by chars/4 underestimates by 2× and trips the spend cap; Exa bills per request for the first 10 results, so asking for fewer costs exactly the same and query count is the only price dial; the front end mints the job id and registers it before invoking, because minting it in Rust meant every event emitted before the IPC reply landed was addressed to an id the window did not know yet and was silently dropped; children are spawned via setsid into their own process group and killed on window-close and app-exit, with a Rust test asserting that a grandchild dies; and PDF export uses createPDFWithConfiguration rather than NSPrintOperation, because calling runOperation from inside a main-thread callback deadlocks when WebKit renders out of process.
Tauri 2とRustのコア。候補を絞り込む処理にはツールを一切与えず、平らに並べた検索結果を一度だけ読ませます。これは意図的な設計です。以前は十数個のヘッドレスのインスタンスに分散し、それぞれが独自に検索を繰り返していましたが、各インスタンスが作業前に約2万1千トークンの準備コストを払い、取得したページを毎ターン送り直していました。ツールなしの一つのインスタンスにまとめたことで、1回の実行費用は約2.15ドルから0.20ドルになりました。モデルの出力は構造的に信用しません。候補のURLはすべてプロンプトに含まれているため、strip_unknown_urlsが、モデルが検索結果から実際に得ていない予約URLや情報URLを削除します。「URLを捏造しない」は、お願いではなく強制です。見つけるのに時間がかかり、今では欠かせない制約がいくつもあります。Tauriは同期コマンドをメインスレッドで実行するため、ネットワークやプロセスのI/Oは別スレッドの補助処理を通さないとウィンドウが固まります。Webから取得したテキストは1トークンあたり約4文字ではなく約2文字のため、文字数÷4でプロンプトを見積もると2倍少なく見積もってしまい、費用の上限に引っかかります。Exaは最初の10件まで1リクエスト単位で課金するため、件数を減らしても費用は変わらず、費用を左右するのは検索回数だけです。ジョブIDはフロントエンドで発行して登録してから呼び出します。Rustで発行すると、IPCの応答が届く前に出たイベントがすべて、ウィンドウがまだ知らないIDに宛てられ、黙って捨てられていたからです。子プロセスはsetsidで独自のプロセスグループとして起動し、ウィンドウを閉じたときとアプリ終了時に終了させます。孫プロセスまで終了することをRustのテストで確認しています。PDFの書き出しにはNSPrintOperationではなくcreatePDFWithConfigurationを使います。WebKitが別プロセスで描画している場合、メインスレッドのコールバック内からrunOperationを呼ぶとデッドロックするからです。
Where it is now
現状と今後Nijikai runs end-to-end today: brief in, shortlist streamed, runsheet out as a PDF. It needs the Claude Code CLI on PATH and an Exa key set in Settings, which is why it is a local build rather than a download — a run costs around ten cents, so Exa's monthly free credit covers roughly a hundred of them.
Nijikaiは現在、最初から最後まで動きます。依頼書を入力すると候補リストが順次表示され、進行表をPDFで出力できます。PATHにClaude Code CLIが必要で、設定画面でExaのキーを入力する必要があるため、配布版ではなくローカル版です。1回の実行は約10セントなので、Exaの毎月の無料クレジットでおよそ100回分をまかなえます。
The interesting constraint in settai software is that the failure mode is social, not technical. A wrong price is an inconvenience; a guest seated with their back to the door, or a host settling the bill in front of them, is the actual product failing. That is why the unknowns are left visibly unknown.
接待のソフトウェアで興味深いのは、失敗が技術的なものではなく社会的なものだという点です。価格の間違いは不便で済みます。けれども、お客様を扉を背にした席に座らせたり、お客様の目の前で会計をしたりすれば、それは製品そのものの失敗です。だからこそ、わからないことはわからないまま、目に見える形で残しています。