Work/SAKURAKO
Desktop · Planning Tool

SAKURAKO

A Tokyo Date Planner for What the Booking Sites Don't List

予約サイトに載らない場所まで探す、東京のデートプランナー

SAKURAKO icon
01

Overview

概要

GetYourGuide and Klook are good at ticketed experiences and useless at 'there is a riverside path in Kagurazaka that is lovely at 4pm and costs nothing'. Sakurako covers both — parks, beaches, festivals, backstreets and free things alongside bookable activities — planned around the hours she is actually free, with an eight-year-old along.

GetYourGuideやKlookはチケット制の体験には強くても、「神楽坂に午後4時がきれいな川沿いの小道があって、しかも無料」といった情報には役に立ちません。Sakurakoはその両方を扱います。予約できるアクティビティと並べて、公園、海辺、お祭り、路地、無料のスポットを、彼女が実際に空いている時間に合わせ、8歳の子ども連れでも楽しめるように計画します。

Built for one specific couple starting from Akebonobashi in Shinjuku-ku, which is why the constraints are concrete rather than configurable. Her week is painted by hand on a 7×16 grid from 07:00 to 23:00 with three states per hour: busy, free with her daughter, free just the two of you. Every free stretch becomes a plannable window with one click, and the window — start, end, travel each way, and the child — is part of the prompt rather than a filter applied afterwards.

新宿区の曙橋を起点にする、ある一組のために作ったアプリです。そのため条件は設定項目ではなく具体的なものになっています。彼女の1週間は、7時から23時までの7×16のマスに手で塗り分けます。1時間ごとに「予定あり」「娘と一緒に空いている」「二人だけで空いている」の3つの状態があります。空いている時間帯はクリック一つで計画できる枠になり、開始・終了時刻、往復の移動時間、子どもの同行といった枠の情報は、後から絞り込む条件ではなく、プロンプトそのものに含めます。

02

How it was built

開発

The aggregators' browse pages return 403 to automated fetches, verified rather than assumed, and there is no public GetYourGuide or Klook API without affiliate credentials — so search genuinely is the only route in. Their individual product pages are indexed, though, so the research reaches them with site: queries instead, and Japanese queries are part of the strategy rather than an afterthought: asoview, craftie and activityjapan carry local workshops that never surface in English results. Two keyless APIs fill in what search should not be asked for — Open-Meteo for the forecast, Nominatim for geocoding.

予約サイトの一覧ページは自動取得に対して403を返します。推測ではなく実際に確認しました。アフィリエイトの認証情報なしで使えるGetYourGuideやKlookの公開APIもないため、検索が唯一の入り口です。ただし個別の商品ページは検索エンジンに登録されているので、site:を使った検索で届くようにしています。日本語での検索も後付けではなく戦略の一部です。asoview、craftie、activityjapanには、英語の検索結果には出てこない地元のワークショップが載っています。検索に任せるべきでない情報は、キー不要の2つのAPIで補います。天気予報はOpen-Meteo、住所から位置への変換はNominatimです。

03

What it does

機能
01
Her Week, Painted by Hand
手で塗る1週間

Three states per hour across a 7×16 grid. The plan is built for the window rather than trimmed to fit it afterwards.

7×16のマスに、1時間ごとに3つの状態を塗り分けます。計画は後から枠に合わせて削るのではなく、最初から枠に合わせて組み立てます。

02
Timed Itineraries
時間つきの行程表

Shortlist a few ideas and a second instance turns them into a real schedule: travel legs, a food stop, buffer, a packing list and a rain backup.

いくつか候補を選ぶと、2つ目のインスタンスが実際のスケジュールにします。移動、食事の休憩、余裕の時間、持ち物リスト、雨の日の代案まで含みます。

03
Vouchers, Marked Honestly
正直に表示するクーポン

It hunts current Klook / GetYourGuide / Voyagin / Asoview promo codes, combo tickets, transit day passes and child-free-entry rules. A code with no source URL it actually visited is shown as Unverified rather than quietly presented as fact.

Klook、GetYourGuide、Voyagin、asoviewの最新のプロモコード、セット券、交通の1日乗車券、子どもの無料入場の条件を探します。実際に訪れた情報元のURLがないコードは、事実のように見せず「未確認」と表示します。

04
Budget in Pounds
予算はポンドで

You think in GBP and the research is given yen. The rate is fetched live on launch from ECB reference data via Frankfurter and falls back to the last stored value offline. Prices show both.

考えるのはポンド、調べる情報は円です。為替レートは起動時にFrankfurter経由で欧州中央銀行の参照データから取得し、オフラインのときは最後に保存した値を使います。価格は両方の通貨で表示します。

05
Booking Links That Are Real
本物の予約リンク

Direct ticket URL, official info page and a Google Maps link on every listing, opened in your actual browser — and any link the model did not get from the search results is stripped rather than shown.

どの候補にも、チケットの直接URL、公式の情報ページ、Googleマップのリンクがあり、普段使っているブラウザで開きます。モデルが検索結果から得ていないリンクは、表示せずに削除します。

06
Honest About Staleness
情報の鮮度に正直

Prices, opening hours and especially voucher codes are read off live pages and go stale. Sakurako marks what it could not confirm instead of guessing. Closing days are the most common trap — many Tokyo museums shut on Mondays.

価格、営業時間、特にクーポンコードは、その時点のページから読み取るもので、時間が経てば古くなります。Sakurakoは確認できなかったことを推測で埋めず、その旨を表示します。最も多い落とし穴は休館日で、東京の美術館や博物館の多くは月曜日が休みです。

04

Under the hood

技術の詳細

The search belongs in Rust検索はRustの仕事

Queries are fired at Exa concurrently, deduplicated by canonical URL and round-robined across queries so a trimmed set still covers every category. Only then does a single toolless instance read them. Removing the agent loop took a search from roughly 30% of a five-hour usage window to about $0.20, and from minutes to seconds.

Exaへの検索を並列で送り、正規化したURLで重複を除き、検索ごとに順番に取り出して、件数を絞ってもすべてのカテゴリーを網羅します。その後で初めて、ツールなしの一つのインスタンスが結果を読みます。エージェントのループをなくしたことで、1回の検索は5時間の利用枠の約30%から約0.20ドルに、所要時間は数分から数秒になりました。

Killing what you spawned起動したものは確実に終了させる

Each job spawns in its own process group via setsid, because a plain kill orphans the CLI's node children. Teardown is guaranteed twice over — both window-close and app-exit events call it — sending SIGTERM to the group, waiting 600ms, then SIGKILL. Closing the app never leaves a paid instance running.

単純にkillすると、CLIの子のnodeプロセスが取り残されます。そのため各ジョブはsetsidで独自のプロセスグループとして起動します。終了処理はウィンドウを閉じたときとアプリ終了時の両方から呼ばれ、グループにSIGTERMを送り、600ミリ秒待ってからSIGKILLを送ります。アプリを閉じた後に、費用のかかるインスタンスが動き続けることはありません。

Reaching what the aggregators block予約サイトが遮断している情報に届く

GetYourGuide, Klook and Viator browse pages return 403 to automated fetches — verified, not assumed — and there is no public API without affiliate credentials. Their product pages are indexed though, so site: queries reach them, and Japanese queries surface asoview and activityjapan workshops that never appear in English results.

GetYourGuide、Klook、Viatorの一覧ページは自動取得に403を返します。推測ではなく確認済みです。アフィリエイトの認証情報なしで使える公開APIもありません。ただし商品ページは検索エンジンに登録されているので、site:検索で届きます。日本語で検索すれば、英語の検索結果には出てこないasoviewやactivityjapanのワークショップも見つかります。

Distrusting the schemaスキーマを信用しない

The final balanced JSON object is extracted from the reply rather than the first — the ideas stream ahead of it, so taking the first would return an idea and lose the summary — and every field is coerced defensively, so a missing price becomes 'not confirmed' rather than a fabricated number.

返答からは最初ではなく最後の完全なJSONオブジェクトを取り出します。候補の情報が先に流れてくるため、最初のものを取ると候補を一つ返して要約を失ってしまうからです。すべての項目は防御的に変換するので、価格がなければ数字を作らず「未確認」になります。

05

Stack

技術構成

Tauri 2 · React 19 · Vite · TypeScript · Tailwind 4. Searching happens in Rust, not in the model: queries are fired at Exa concurrently, deduplicated by canonical URL and round-robined across queries so a trimmed set still covers every category, then handed to one instance launched with no tools at all. That replaced a fan-out of about a dozen instances each running their own search loop — a structural cost rather than a model one, since every instance paid a measured ~21k tokens of harness overhead before doing anything and re-sent each fetched page on every later turn. Removing the agent loop took a search from roughly 30% of a five-hour usage window to about $0.20, and from minutes to seconds. Instance lifecycle is the careful part: each job spawns in its own process group via setsid so the CLI and its node children die together, progress is streamed by parsing the stream-json line protocol including partial deltas (with only one turn, nothing would appear until the whole reply landed), every instance runs under a hard budget ceiling where hitting the cap keeps what already streamed and reports the run as capped, teardown is guaranteed twice over from both window and app exit events with SIGTERM then SIGKILL, and a per-job watchdog kills anything past the timeout. The instance is confined to the web — search and fetch pre-approved, everything that reads disk, executes, delegates or schedules on an explicit deny list, cwd in a temp directory. Replies are never trusted to match the schema: the final balanced JSON object is extracted from the reply, and every field is coerced defensively so a missing price becomes 'not confirmed' rather than a fabricated number.

Tauri 2・React 19・Vite・TypeScript・Tailwind 4。検索はモデルではなくRustで行います。Exaへの検索を並列で送り、正規化したURLで重複を除き、検索ごとに順番に取り出すことで、件数を絞ってもすべてのカテゴリーを網羅します。その結果を、ツールを一切持たない一つのインスタンスに渡します。以前は約十数個のインスタンスがそれぞれ検索を繰り返す分散型でした。問題はモデルではなく構造にありました。各インスタンスが作業前に実測で約2万1千トークンの準備コストを払い、取得したページを毎ターン送り直していたのです。エージェントのループをなくしたことで、1回の検索は5時間の利用枠の約30%から約0.20ドルに、所要時間は数分から数秒になりました。注意を要するのはインスタンスの管理です。各ジョブはsetsidで独自のプロセスグループとして起動し、CLIとその子のnodeプロセスが一緒に終了するようにしています。進捗はstream-jsonの行形式を部分的な差分まで解析して逐次表示します。ターンが1回だけなので、そうしないと返答全体が届くまで何も表示されないからです。各インスタンスには厳格な予算上限があり、上限に達した場合はそれまでに届いた内容を残して「上限で停止」と報告します。終了処理はウィンドウとアプリ両方の終了イベントから二重に保証し、SIGTERMの後にSIGKILLを送ります。ジョブごとの監視役が、制限時間を超えたものを終了させます。インスタンスの行動範囲はWebに限定しています。検索と取得のみを事前に許可し、ディスクの読み取り、実行、委任、予約に関わる操作はすべて明示的に拒否し、作業ディレクトリは一時フォルダです。返答がスキーマどおりだとは決して信用しません。返答から最後の完全なJSONオブジェクトを取り出し、すべての項目を防御的に変換するため、価格がなければ数字を作らず「未確認」と表示します。

06

Where it is now

現状と今後

Sakurako builds to a DMG but is deliberately unlisted: it is unsigned, so Gatekeeper refuses it on first launch, and it needs both the Claude Code CLI and an Exa key to do anything at all. The rearchitecture that cut a search roughly tenfold is in and holding; signing it properly is a paid-developer-account decision rather than an engineering one.

SakurakoはDMGとしてビルドできますが、あえて公開していません。署名していないため初回起動時にGatekeeperに拒否されること、そして動かすにはClaude Code CLIとExaのキーの両方が必要なことが理由です。検索費用をおよそ10分の1にした設計の見直しは導入済みで、安定して動いています。正式な署名は技術的な問題ではなく、有料の開発者アカウントを契約するかどうかの判断です。

The rewrite is the lesson here. The first version looked sophisticated — a dozen agents, each with its own search loop — and it was slower, twenty times more expensive, and no better. The work an agent loop does well is deciding what to look for; the looking itself is a job for ordinary deterministic code.

ここでの教訓は作り直しそのものです。最初の版は、それぞれ検索ループを持つ十数個のエージェントという高度な構成に見えましたが、遅く、費用は20倍で、結果も良くありませんでした。エージェントのループが得意なのは「何を探すか」を決めることで、探す作業そのものは普通の決定的なコードの仕事です。

Next case study 次の実績
EURO-JAPAN COMPASS
Two Language Environments, Not One Bilingual Page
→