|
| 1 | +--- |
| 2 | +title: "Didi crawl — three targets (links, streams, team members), the relevance brief, and staged team ingest" |
| 3 | +lede: "Implements the spec's §v1.2 crawl contract: one organization.crawl capability in prompt-runner riding Anthropic server-side web_search, candidates rendered through search-and-add's crawl mode (links/streams) and a staged people section on the workbench (team), all driven by a per-workspace operator-editable relevance brief." |
| 4 | +date_created: 2026-07-24 |
| 5 | +date_modified: 2026-07-24 |
| 6 | +authors: |
| 7 | + - Michael Staton |
| 8 | +augmented_with: |
| 9 | + - Claude Code on Claude Fable 5 |
| 10 | +semantic_version: 0.0.0.1 |
| 11 | +tags: |
| 12 | + - Plan |
| 13 | + - Augment-It |
| 14 | + - Org-Workbench |
| 15 | + - Search-And-Add |
| 16 | + - Didi-Chat |
| 17 | + - Crawl |
| 18 | + - Relevance-Brief |
| 19 | +status: Implementing |
| 20 | +--- |
| 21 | + |
| 22 | +# Didi crawl — the v1.2 build |
| 23 | + |
| 24 | +## Spec reference |
| 25 | + |
| 26 | +Implements [[../specs/Augment-From-DB-Flow]] §v1.2 (v0.1.2.0). Composes with |
| 27 | +[[Didi-Chat-In-Org-Workbench-Verify-Team-Page-Into-People-Objects]] (which |
| 28 | +keeps the chat-rail context plumbing). Branch: `rebuild/turbo-rsbuild`. |
| 29 | + |
| 30 | +## Decisions (closing the spec's v1.2 open questions, grounded in a three-way code read) |
| 31 | + |
| 32 | +1. **Crawl substrate: Anthropic server-side web_search in prompt-runner** — |
| 33 | + NOT the social-search packs. `prompt-runner` is the LLM gateway |
| 34 | + (workspace never touches the SDK, by invariant `chat.ts:8-10`), and it |
| 35 | + already wires `web_search_20260209` with the `pause_turn` resume loop |
| 36 | + (`request.ts:27-30`, `anthropic.ts:66-70`). One model call composes |
| 37 | + queries, searches, filters by the brief, and returns structured |
| 38 | + candidates — the packs/connectors stay the manual 🔍's substrate. The |
| 39 | + social-search option (7 social packs + firecrawlScrape + Haiku fallback) |
| 40 | + is the documented fallback if model-driven quality disappoints. |
| 41 | +2. **Brief storage: server-side, resolver-owned** (shipped ahead of this |
| 42 | + plan): `relevance_briefs` table, `client.brief.get`/`set` capabilities. |
| 43 | + The crawl handler reads the brief itself over NATS (single source of |
| 44 | + truth for button and chat doors); the UI editor is a workbench panel. |
| 45 | +3. **Candidate UI: search-and-add crawl mode** for links/streams — the |
| 46 | + Phase-5 scan-mode precedent exactly (`crawl?: boolean` on the envelope, a |
| 47 | + third fire path, same ResultsList/ResultRow, per-row ➕ already routed by |
| 48 | + `verbFor(target)`). Team members do NOT go through search-and-add — they |
| 49 | + stage on the workbench's people reveal with per-row accept |
| 50 | + (no-candidate rows flow: `person.apply` create + `person.affiliate`; |
| 51 | + ambiguous rows open the candidate gate — the didi-chat plan's step 4 |
| 52 | + discipline). |
| 53 | +4. **Doors:** crawl is a header-level action (the whole list is the |
| 54 | + subject, unlike per-entry scan): AdditiveList gains an optional |
| 55 | + `oncrawl` header button (links + streams lists); PeopleReveal gains a |
| 56 | + "crawl team" header button; chat door = `organization.crawl` added to |
| 57 | + `CHAT_CAPABILITY_NAMES` + a verb-slab entry (focused-org context |
| 58 | + plumbing stays with the didi-chat plan). |
| 59 | +5. **Org context is fetched server-side**, not stuffed into envelopes: the |
| 60 | + crawl handler NATS-requests `organization.detail` (name, domains, |
| 61 | + existing list URLs for dedupe) and `client.brief.get`, then prompts. |
| 62 | + |
| 63 | +## Capability contract |
| 64 | + |
| 65 | +`organization.crawl` → `organization.crawl.requested` (prompt-runner), |
| 66 | +timeout 300s. Input `{ org_slug, target: 'links'|'streams'|'team', client, |
| 67 | +max_results? }`. Replies: |
| 68 | + |
| 69 | +- links/streams: `{ ok, provider: 'didi-crawl', results: ConnectorResult[] }` |
| 70 | + (url/title/content — content carries the model's one-line relevance |
| 71 | + reason; deduped against the org's existing URLs server-side). |
| 72 | +- team: `{ ok, people: [{name, role, headline?, linkedin_url?, bio_url?}], |
| 73 | + filtered_note, source_urls[] }` — selection per the brief's people policy |
| 74 | + (default: all major leadership + all team members covering Education & |
| 75 | + Workforce Development and related strategies/topics), with the |
| 76 | + filtered-out count named, never silent. |
| 77 | + |
| 78 | +## Steps |
| 79 | + |
| 80 | +1. **prompt-runner: `crawl.ts`** — `registerCrawlHandler(nc)`: fetch |
| 81 | + `organization.detail` + `client.brief.get` over NATS; per-target system |
| 82 | + prompt (org identity + existing URLs + brief + strict JSON output |
| 83 | + contract); `buildRequest` with `tools:['web_search']`; `runPrompt`; |
| 84 | + tolerant JSON extraction (fence-stripping); dedupe vs existing; reply. |
| 85 | + Register in prompt-runner `server.ts`. |
| 86 | +2. **workspace:** capability map + 300s timeout; `organization.crawl` into |
| 87 | + `CHAT_CAPABILITY_NAMES` + a WORKBENCH slab line describing the three |
| 88 | + targets and args. |
| 89 | +3. **search-and-add crawl mode:** `crawl?: true` on `SearchRequestDetail` |
| 90 | + (both type mirrors); `crawlSearch` wrapper; `crawlMode` derived + third |
| 91 | + fire path + status bar arm in App.svelte (scan-mode pattern verbatim). |
| 92 | +4. **org-workbench doors:** AdditiveList `oncrawl` header prop (🤖); |
| 93 | + OrgCard passes it for links + streams (envelope `{entity, target, |
| 94 | + seed_term:'', crawl:true}`); BriefPanel (header toggle → textarea → |
| 95 | + `client.brief.get/set` wrappers). |
| 96 | +5. **Team staging:** PeopleReveal "crawl team" button → `crawlOrg(team)` → |
| 97 | + staged rows (name · role · links) with per-row Accept/skip; |
| 98 | + accept → `person.candidates` → empty: `person.apply`(create) + |
| 99 | + `person.affiliate` (role, source = team-page URL); non-empty: inline |
| 100 | + candidate gate (pick match or create). `filtered_note` rendered under |
| 101 | + the staged section. |
| 102 | +6. **Verify:** typechecks + svelte-checks + builds; rebuild prompt-runner, |
| 103 | + workspace, resolver containers; live smoke of `organization.crawl` |
| 104 | + against the Aspen safe target if NATS reachable, else operator |
| 105 | + walk-through: Gates Foundation → crawl links (candidates appear, ➕ |
| 106 | + one) → crawl streams → crawl team (staged people per policy) → edit the |
| 107 | + brief and re-crawl to see selection change. |
| 108 | +7. Changelog + gh issue close + spec revision note. |
| 109 | + |
| 110 | +## Non-goals (this pass) |
| 111 | + |
| 112 | +- Chat-rail focused-org context and conversational triggering UX — the |
| 113 | + didi-chat plan owns it; this pass only makes the capability chat-legal. |
| 114 | +- Headshots/enrichment of staged people (person-enrichment owns). |
| 115 | +- Corpus-target crawl (search-and-add's manual flow already covers corpus; |
| 116 | + revisit after the metadata work in the corpus-items issue). |
0 commit comments