@hireobi/cli 0.0.0-stage → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,107 @@
1
+ # Host work and result files
2
+
3
+ `obi harness show --stage <business|questions|strategies> --json` reads the active work from the authenticated case. Use the returned work ID, version, input version, domain, allowed sources and context. Add `--eval-case <id>` for a public-company case. `pending` requires host work; `submitted` is saved state, not an invitation to repeat acquisition. The same work can be shown after another session.
4
+
5
+ For a large Business inventory, `show` gives compact progress counts and a first sample of sources or candidates; it is not the full 5,000-page corpus. Retrieve saved facts on demand with `obi harness evidence --work-id <UUID> --kind claims --query <focused text> --json` or retrieve text with `--kind sections`. To read a whole filter set in one pass, add `--all --output <file>`: every page lands in one local JSON file (`items`, with the server's `coverage`), which subagents can split and read in parallel. For Strategies work, `--kind measurements` reads the pinned selected scan, including full answer text in bounded `answerText` slices and source references. A measurement can repeat across pages; reconstruct its answer by `answerTextOffset` through `answerTextTotalChars` and its reference list by `sourceRefOffset` through `sourceRefCount` before claiming either is complete. The `excerpt` is only a preview. `--url <exact URL>` narrows a kind. Continue with the returned `nextCursor` and the same kind, query and URL until it is null; a cursor cannot be reused with different filters. Add the case's `--eval-case` when needed. Each page is bounded, so search and read relevant evidence instead of pasting the entire corpus into the host conversation. Keep the returned claim or measurement IDs and source URLs for question or strategy references.
6
+
7
+ ## Reading scope: minutes to a first review
8
+
9
+ The declared inventory is a scope, not every URL of the site. `research crawl` declares it: the chosen market's locale (`--locale fr`), the priority page types (`--pages priority`, the default: everything except blog and news), and a cap on pages read best types first (`--max-pages 150`, the default for a new inventory). Pages outside the scope stay listed as `deferred` in the checkpoint; they are not read, not failed, and never counted as gaps. Pass the locale of the market the customer named; when the home page is the only language, leave it unset.
10
+
11
+ Closure follows the scope: `result.complete` is true once the scoped frontier has no pending URL, every acquired page has its interpretation outcome, and each failed or blocked URL is a recorded terminal gap. Report `deferred` with the other counts, in one line: "read 112 pages of the French site; 3,480 listed pages (other languages, blog) are not read". The customer widens the scope with words ("lis aussi le blog", "read the German pages"): rerun the same crawl with `--pages all`, another `--locale`, or `--max-pages none`; deferred pages become pending again and a new Business work after confirmation absorbs them without reopening the confirmed review.
12
+
13
+ Read in parallel when the host offers subagents: acquisition stays one process per checkpoint file, but the interpretation of acquired pages splits by page ranges of the corpus, and each rival has its own checkpoint, so own site and rivals acquire at the same time. Each subagent returns claims with their exact quotes and URLs; the main conversation assembles the submission file.
14
+
15
+ ## Adaptive research and tool recovery
16
+
17
+ When the customer asks you to resume after an error or a stop, say in your reply, in plain words: which work you resume (the selected case, the work ID and its version), that you first re-read its current state (`harness show`, and `status` for the case) before any retry or write, that you continue from the saved checkpoint rather than starting over, and that each partial save carries a new replay key. Name the exact error the customer saw. Describe commands and tools in plain words in the reply; give a command line or a tool such as `/chrome` only when the customer has to run it, and keep the Obi harness out of the customer's way.
18
+
19
+ Reuse relevant project context already authorized for this mission, keeping its source, date and uncertainty. Private context remains distinct from public site evidence and customer-confirmed facts. Read priority home, product, pricing, legal and buyer-specific pages first. As soon as they support an offer and buyer hypothesis, show it with citations and material gaps; keep it provisional while continuing the scoped inventory, and submit the first batch as soon as the home, pricing, product and about pages are interpreted so the server holds partial evidence. A useful conversational pattern is “I found X; it matters for your buyer because Y; I am checking Z next”, in the customer’s language, without another consent question. Separate positioning, buyer situations and served markets from legal identity and office location using [business judgment](business-judgment.md). Choose the next investigation by the missing fact that changes the buyer decision, rather than a fixed intake questionnaire.
20
+
21
+ Discover exposed host tools when that investigation needs search or a JavaScript page. Prefer an available native tool; its first real query/read is the capability check. Retain the tool name, actual provider if disclosed, query, locale if supplied, requested/final URLs, acquisition method, observed time, available metadata and errors. An unspecified engine stays unspecified; a generic search cannot be labelled Google. If Google-specific evidence matters, obtain an available Google surface or keep that question unresolved. A successful browser read proves only that browser's view.
22
+
23
+ When a tool is absent or exhausted, name the affected fact and give one concrete recovery matched to the host, its permissions and quota. Continue HTTP-accessible pages with `research crawl`, or reason from saved evidence, while the JavaScript-dependent gap remains visible. For Claude Code with an existing Chrome integration, `/chrome` can inspect/reconnect it; request a dedicated public-research browser profile when supported, since the integration shares that browser's login state. Describe the actual enabled profile and traffic controls; promise ephemeral isolation only when the tool provides it. The customer chooses browser access and site permissions; explain harness usage/quota without promising free or unlimited access. Check the [official Chrome instructions](https://code.claude.com/docs/en/chrome) before advising setup. For another host, consult its exposed tool instructions and official documentation instead of applying Claude commands. An extra local browser/dependency requires the customer's scoped setup choice; keep global settings, personal sessions and paid search subscriptions outside routine research authority. Do not open a software-development task to recover customer research.
24
+
25
+ Keep separate checkpoint/corpus paths for the own site and each selected rival. Resume each from its saved state; recheck the saved frontier after every crawl batch or rendered import, since new links can add pending URLs. Interpret both previously acquired and newly acquired evidence, prioritizing what changes the buyer decision. For each acquired page, retain its interpretation or an explicit duplicate/out-of-scope/no-material-evidence outcome; acquisition alone is not interpretation. Retry temporary failures with the existing helper flag and retain robots disallows and errors. Report discovered, acquired, interpreted, blocked/failed and pending counts separately. Use focused evidence retrieval for the current buyer question. File/batch safety bounds are not coverage claims. Save partial submissions during active work. A stopped harness resumes from saved state; it does not continue local reading in the background.
26
+
27
+ Keep acquisition status separate from Business-work closure. The crawl helper's `complete` remains false while its errors exist; preserve that returned flag and its coverage. A submission's `result.complete` closes the declared scope (see [reading scope](#reading-scope-minutes-to-a-first-review)) only after its latest own/rival frontiers contain no pending URLs, every acquired page has an explicit outcome, and each failed/blocked URL is recorded as a terminal gap. Pending interpretation, a retryable failure still awaiting its retry, or newly discovered in-scope pages keep the work partial; deferred pages do not. A terminal robots disallow can be accounted for without being acquired or interpreted. Closing the scope preserves the gap visibly; it proves neither every website URL was discovered nor every page was readable.
28
+
29
+ ## Pages a visitor sees differently
30
+
31
+ Each `research crawl` result lists `renderingNeeded`: home, pricing and product pages whose raw HTML likely hides what a visitor sees. Handle every listed page before the first proposal:
32
+
33
+ | Reason | What to do |
34
+ |---|---|
35
+ | `client_rendered`, `thin_text` | Render the page in the host browser and import it with `research capture` (see [native capture](native-capture.md)), then interpret the rendered text. |
36
+ | `billing_toggle` | Capture the rendered page, then switch each billing or currency control and note every visible price with the control state used. Prices that differ from the raw HTML are evidence seen in the browser: use them in your reading with the control state, and save the rendered capture. |
37
+ | `visual_media` | Screenshot the page and inspect logos, animations and illustrations. First search the saved text for the same fact (`harness evidence --kind sections --query <name>`): a demo widget often carries it as text. A fact found only in an image is an observation: note it in your reading as `seen on <url>, <date>, screenshot <file>` with your confidence, and keep the screenshot in the working folder so the customer can open it. It informs your reading of the ICP and personas as an inference. Page claims quote page text, so a visual-only fact never becomes a claim, and it never goes into a buyer profile: a profile's `sourceRefs` must cite a source that states what the profile says, and no published claim states a fact seen only in an image. State the fact in the conversation, labelled as seen in an image, until the server can store screenshot evidence. Ask the customer only when the fact would change the questions and your confidence is low, inside the single pre-filled question. |
38
+
39
+ Prices, plans and wording can change with the visitor's country. When the pricing page shows a currency or plan list that does not match the selected market, and the host's browser or search tool can present another location, read the page for the selected market too and record the location, currency and language of each view in the conversation. `research capture` keeps one rendered observation per URL, and a second capture replaces the first, so import only the view that matches the selected market and describe the other views as observations in the conversation. When the host cannot change location, say so in the reading and treat the price as that browser's view only. A price that differs by country is evidence for the market it was seen in, never for every market.
40
+
41
+ Read videos without being asked. Each crawl lists `videos`: the YouTube, Vimeo, Loom, Wistia and site-hosted videos its pages embed or link, the most widely shown first. It also lists `videoChannels`, the YouTube channels the site links, often only in its footer. Open each channel's videos and pick the product demos, tours and talks. When the site links no channel, search for one, and for the founders' demo posts on X or LinkedIn and third-party reviews or walkthroughs; `research video` reads an X video post too. For each product demo, tour or founder pitch, run `obi research video <url> --output <directory> --frames 8 --json`: `transcript.json` holds the timed transcript, and the frames show the screens, workflows, logos and integrations, which a demo often shows without saying. Look at every frame. For a long talk or webinar, captions alone (`--frames 0`) are usually enough. The transcript comes from the video's captions, published or automatic. A video without captions yields frames only, and the result says so.
42
+
43
+ The command runs only on this machine, with `yt-dlp` (and `ffmpeg` for frames), which `obi doctor` reports. When a tool is missing, ask the customer before installing it, and meanwhile read the captions in the host browser. Cite a video fact with its URL and timestamp. A visual-only fact in a video follows the image rule above, with the timestamp in place of the screenshot.
44
+
45
+ ## Submission and capture provenance
46
+
47
+ The host creates one local JSON submission file with this top-level structure:
48
+
49
+ ```json
50
+ {
51
+ "stage": "business",
52
+ "version": 1,
53
+ "workId": "<UUID from show>",
54
+ "workVersion": 1,
55
+ "inputVersion": 2,
56
+ "replayKey": "<new UUID retained for this exact result>",
57
+ "result": {}
58
+ }
59
+ ```
60
+
61
+ Copy the actual identifiers and versions from `show`; the displayed numbers are illustrative. The Business file limit is 32 MiB; Questions and Strategies are limited to 8 MiB. Split larger business inventories into smaller batches. Keep it in the customer's authorized workspace or a private temporary directory, then submit with `obi harness submit --stage <stage> --input <file> --json`. A network timeout means read the stage again, then retry the exact file only if needed. `HARNESS_WORK_CONFLICT` requires reviewing the current context and prerequisites before creating a new result. The server validates source scope, versions and content independently.
62
+
63
+ When the host's native browser cannot enumerate a public site, use the packaged pure acquisition helper:
64
+
65
+ ```bash
66
+ obi research crawl <domain> --checkpoint <checkpoint.json> --output <corpus.json> --batch-size 25 --locale <market language> --json
67
+ obi research readiness --input <corpus.json> --output <readiness.json> --json
68
+ ```
69
+
70
+ `crawl` fetches one bounded batch and atomically saves its page corpus and inventory checkpoint. Continue the same command while URLs remain. After a temporary fetch or robots failure, use the same paths with `--retry-failed` to reattempt failed and temporarily blocked resources; a robots disallow stays blocked. When the public site has changed and the customer wants a new reading, use `--refresh` once, then resume without it. The refreshed inventory reacquires pages in batches while keeping older dated captures in the local corpus. Pending or failed refresh pages are not current evidence; `readiness` assesses only reacquired pages and reports incomplete coverage until the refresh finishes. Neither flag starts a paid scan or asks for new permission within the agreed public mission. The two files stay local until selected sourced extracts are included in a harness submission. The host may add `pageViews` (paired raw/rendered observations) and `businessFacts` (confirmed key, value, source URL) to the corpus before `readiness`; preserve their sources. Neither command uses the product account or an engine supplier.
71
+
72
+ When the installed CLI advertises `research capture` in its capabilities/help, read [native capture input](native-capture.md) to construct its complete paired `SelectedPageEvidence` from actual browser metadata and the unchanged raw observation. Use the same domain's saved checkpoint and corpus:
73
+
74
+ ```bash
75
+ obi research capture --input <selected-page.json> --checkpoint <checkpoint.json> --output <corpus.json> --json
76
+ ```
77
+
78
+ This is a local no-network import, not a browser or a replacement crawl. Supply actual raw/rendered observations in the existing contract; the importer selects the rendered source, recomputes extraction/hashes and preserves the raw counterpart in `pageViews`. Read its returned revision and saved state before Business submission. If the installed version does not advertise this action, keep using its supported acquisition workflow and report the rendered handoff unavailable; do not promise it was saved. If a native tool returns DOM HTML without navigation status, keep that attempt partial; obtain the missing metadata from that same browser navigation through an exposed response/trace capability, or explain the specific additional local tool required and obtain scoped setup approval. A separate HTTP fetch cannot certify that navigation. Retain usable HTTP evidence meanwhile. The import validates input consistency and scope, not the authenticity or network safety of prior browser traffic; use the host's actual public-site, redirect and subresource controls during acquisition.
79
+
80
+ For business work, send the selected company's own-domain `HostCrawlState` as `result.state` and at most 40 public pages per submission. Competitor and independent media pages are separate captures; do not merge their crawl state into the company's inventory. After confirmation, a new Business work unit can acquire scoped media and refresh own or rival pages without reopening the confirmed review. Keep `result.state` anchored to `https://<own-domain>` with a work-local revision; the server's current work defines the permitted roles and sources. A `role: "media"` page must match an exact URL in `work.allowedSources.externalUrls` or an exact server-provided search mission from `work.allowedSources.externalSearchQueries`. For each independent page, add `result.externalSourceRefs` with `{kind: "scan_citation",url}` or `{kind: "search_mission",url,query}` as appropriate. Search missions are scoped prompts to the host search tool, not proof that the page was independently retrieved by an engine. Copy each captured page's `url`, `pageType`, `observation`, `content`, `contentHash` and `unchanged` without rewriting its observation. Add `role` (`own`, `competitor` or `media`) and sourced `claims`. `observation.body` is the fetched HTML, while `content` is the helper's extracted title, metadata, sections and structured facts; the server recomputes the extraction and checks both hashes. If the native browser captured paired raw and rendered observations for an acquired own page, include up to 40 matching `SelectedPageEvidence` entries as `result.pageViews` (same URL and matching selected raw or rendered observation ID and body hash); the server computes rendering Readiness from them. Do not submit an invented Readiness report in the result. A rendered page needs actual DOM HTML and its own browser observation metadata. Preserve requested/final URL, observed status, method, time, body and computed hash separately for raw and rendered attempts. Missing status stays null and cannot certify a successful complete rendered capture. An accessibility excerpt, screenshot or summary is partial evidence, not full captured HTML; another HTTP request cannot supply a render's missing status or time. Save claims against their accepted captured observation and verify their persisted source with scoped `harness evidence` before using them downstream. A rendering difference in Readiness alone does not save a JavaScript-only Business claim. Never invent an HTTP status, render, timestamp or hash. Each claim has its exact `quote`, `attribute`, `value`, `subjectCompany` and `subjectProduct`; the quote must occur verbatim in the extracted text. Set `epistemicStatus` to `inferred` for an interpretation rather than a direct statement. For a buyer cohort, put the same `segment` label on its `audience` claim and on at least one cited `job`, `trigger`, `pain` or `outcome` claim; two distinct cohorts need two such pairs. A broad audience summary is not a third cohort: omit a separate unsegmented audience claim when it would create a segment without a situation. A `stated` segment label must appear in its quote; use `inferred` for a supported interpretation, and ask the customer if the cohort is uncertain. Set `geographyScope` to `served_market` only when the page actually supports a served geography; an office address alone does not. For grounded `attribute: "company"` claims, set `identityKind` to `visible_brand` for the public brand or `legal_name` for a cited legal name; keep the two separate. A title or OG site-name signal alone identifies visible branding, not a legal entity. The other optional claim fields default to `stated` and null.
81
+
82
+ `result.candidates` records each public rival domain with `decision` (`direct`, `rejected` or `undecided`) and a source-based `reason`. Mark a rival `direct` only when its own home, product or pricing pages carry four distinct stated claims you submit with `role: "competitor"`: an `offer`, an `audience`, a `job` and a `geography` claim with `geographyScope: "served_market"` whose quote states where it sells. `inferredOverlap:{offer,buyer,job,market}` relaxes the wording match between those claims and the selected company's facts; it never replaces a missing claim, so one refused `direct` rival refuses the whole batch. Most rival sites never state a served market: then use `undecided`, give the source-based reason, and let the customer choose that rival with `business add-competitor` once discovery is published. A rival with conflicting evidence is `rejected` with its reason. Search beyond the customer's comparison page and distinguish the provider actually used. `result.coverage` contains discovered, acquired, interpreted and retrievable counts plus typed errors `{url,code,message}`; `url` may be null for a source-independent failure. Translate helper errors to the supported codes `fetch_failed`, `render_failed`, `parse_failed`, `quota_exhausted`, `capability_missing`, `timeout` or `other`, preserving the original reason in `message`. Always set `result.complete` explicitly: `false` while batches, pending interpretation or unresolved inventory outcomes remain; `true` only under the declared-inventory closure criteria, with terminal gaps preserved in coverage. A partial submission saves its batch and keeps the same work pending; send the next batch with the same work ID and a new replay key. At least the selected company's public home page must be acquired before a business submission. After final submission, inspect `business show` for completed discovery and source-backed direct rivals before asking the customer to confirm.
83
+
84
+ For questions work, read [questions](questions.md#writing-the-questions) for the method, then ask the customer to select `result.marketIso` from `data.context.supportedMarkets` and a primary buyer from `data.context.audienceChoices`. `data.context.suggestedMarketIso` may be shown but must not be silently selected. Set `result.primaryAudienceIndex` and `result.primaryAudienceClaimId` from that buyer choice, and `result.target` to the confirmed company name. For ordinary work, provide exactly `data.context.coveragePlan.questionCount` questions. To review an intended purchase of N packs, use `obi harness show --stage questions --packs N --json`, repeat `data.context.purchaseReview.intendedPacks` as `result.intendedPacks`, and provide exactly its `questionCount` (25N). This stages a candidate for review while the displayed active coverage and prompt set remain unchanged; payment must activate the paid scope before collection, through [purchase](purchase.md). Each question needs `id`, `intent` (`category_discovery`, `comparison`, `use_case`, `feature`, `alternatives` or `trust`), nullable `facet`, `text`, `buyingEligible`, `buyingReason` and two to eight `sourceRefs` taken from `data.allowedSourceRefs`: the selected audience claim ID plus at least one fact read from the evidence export (`data.context.evidence`). A paired question also carries `arm`, `pairId` and, on its context twin, `profile` ([buyer profiles](questions.md#buyer-profiles-and-the-coldcontext-pairs)). Use at least three intents and enough buying-eligible questions for the displayed plan: at least three, or more when its Claude or Perplexity subset requires them. Keep IDs and wording distinct. On a wording edit, preserve the selected market, audience index and claim ID as well as the existing question IDs, intents, facets, arms, pair IDs and profile IDs. Mix natural buyer situations and concise searches. Preserve the selected buyer's tasks, constraints and purchase triggers; do not copy promotional claims into the question voice. A candidate set is not a scan.
85
+
86
+ For strategies work, use the current measured answers, cited/retrieved sources, own pages, rival pages, Readiness findings and customer feedback provided in the work context. Submit `result.candidates` with four to twelve distinct `{id,action,priority}` entries. Each `action` has `kind`, `title`, `why`, `evidenceTier`, `impact`, `effort`, `ifSkipped`, and `signalRef` with `targetUrl`, `nextStep`, `uncertainty` and evidence `{type,id,sourceUrl}` drawn from the current work context. For a selected rival page, use `type: "competitor_page_fact"` with the persisted claim UUID and its page URL from `data.context.competitorFacts`; keep this separate from independently retrieved `externalFacts`. Each `priority` has `urgency`, `buyerImportance`, `missingPiece`, `speed`, `evidence`, `effort` and a concrete `reason`. `result.selected` names exactly three `{id,outranksId,whyNow,outranks}` comparisons, with each selected ID outranking an unselected candidate. A candidate must name an affected asset, observation, proposed action, uncertainty, effort and verification criterion. A declined idea stays declined unless changed evidence justifies revisiting it. Persist the result before calling the priorities saved.
87
+
88
+ Each `signalRef` also carries `evidenceLevel` `{level, basis, verify}`: how solid the link between the action and its expected effect is, what supports it, and how the customer checks the result (usually a rerun of the scan on the named questions). Set `evidenceTier` from the level; the server refuses any other pairing.
89
+
90
+ | `level` | When | `basis` names | `evidenceTier` |
91
+ |---|---|---|---|
92
+ | `documented` | The engine's own published documentation states the mechanism | The documentation URL | `proven` |
93
+ | `measured` | This business's measured answers or own site show it | The measured numbers or page | `contested` |
94
+ | `studied` | A third-party study or benchmark observed it, as a correlation or in a test setting | The study, its size and its date | `contested` |
95
+ | `hypothesis` | Plausible reasoning or a vendor claim that nothing has shown yet | Why it is plausible | `speculative` |
96
+
97
+ A cited domain measured in the answers supports an action without showing that acting on it works; a study result or vendor claim is never `documented`. The GEO paper's gain from statistics, quotations and cited sources is `studied`: observed in a benchmark with supplied sources, not on live engines. Between two levels, choose the lower.
98
+
99
+ ## Saved drafts, corrections and export
100
+
101
+ Write new content with [drafting](drafting.md). For an ordinary connected business, use `obi harness artifact show --work-id <task UUID> --json` before drafting or correcting a saved strategy task. This work ID is the strategy task ID, not the Business/Questions/Strategies submission-unit ID. A null revision means `expectedVersion: 0`; otherwise use the returned immutable revision version. Artifact commands default to the connected business account. For an operator evaluation, pass its explicit `--eval-case <id>` on every save, show, history and export call; a missing or foreign case is refused.
102
+
103
+ Write one local JSON file with `replayKey` (new UUID retained for this exact save), `expectedVersion`, `author` (`agent` for your draft, `user` for the customer's own edited content), `format` (`markdown` or `html`), the complete `content`, and `provenance: {"source":"customer_harness","harness":"<actual host name>"}`. A user edit can include `note` containing the customer's stated reason; an agent draft omits it. Content is limited to 40,000 characters; keep the complete input JSON within the CLI’s bounded request size.
104
+
105
+ Save through `obi harness artifact save --work-id <task UUID> --input <file> --json`. A lost response calls for the exact same file and UUID: the server returns the original revision without adding another one. A stale-version conflict calls for reading the latest content, applying the requested correction and saving with its version and a new UUID. A bare request to correct opens the conversation; wait for the customer's words before changing content or assigning a reason.
106
+
107
+ Resume with `show`; inspect `obi harness artifact history --work-id <task UUID> --limit 25 --json`, continuing its returned cursor if necessary. Retrieve one immutable version with `show --revision <n>`. Export with `obi harness artifact export --work-id <task UUID> --revision <n> --output <new file> --json`; the destination must not exist. Report saved content only after the server returns that revision. A saved or exported draft is available for review and does not establish publication, implementation or measured impact.
@@ -0,0 +1,99 @@
1
+ # Native rendered capture input
2
+
3
+ Read when constructing `research capture` input or diagnosing a rejected native render. The installed CLI advertises the action and file arguments, not this object shape. Use only public URLs allowed by the mission and the browser's actual traffic controls. The import performs no network request and cannot authenticate prior browser traffic.
4
+
5
+ ## Acquire the observations
6
+
7
+ Run the supported crawl first so the exact selected URL is in the saved page inventory and its raw observation is in the corpus. Keep its checkpoint and corpus paths. For a JavaScript page, use an available browser to capture full DOM HTML, requested URL, final URL, the navigation response's actual status and the actual capture timestamp (ISO 8601 with offset). A browser summary or screenshot is insufficient. Record headers only if exposed; an empty object means none recorded.
8
+
9
+ For example, an existing Playwright-capable tool can return `requestedUrl`, `finalUrl`, `status`, `observedAt` and `body` from `page.goto(url)`, its returned response's `status()`, `page.url()`, `new Date().toISOString()` at capture time, and `page.content()`. Wait for the relevant rendered content before capture. This is an example of tool output, not a new Obi schema or an instruction to install a browser. Consult that tool's official instructions. A null response/status or truncated tool output remains incomplete; keep the usable HTTP evidence and explain the exact missing browser capability. Never supply a different HTTP request's status or a later invented timestamp.
10
+
11
+ ## Complete success shape
12
+
13
+ The input is one JSON object, without a submission envelope:
14
+
15
+ | Field | Successful paired capture |
16
+ |---|---|
17
+ | `pageId` | `page:` followed by lowercase SHA-256 of the selected URL serialized by `new URL(url).href` after setting its `hash` to `""` |
18
+ | `url` | Exact selected URL already present as a page in the saved inventory |
19
+ | `raw` | Entire saved raw observation, unchanged, including optional-field presence |
20
+ | `rendered` | Distinct observation for the actual browser navigation, with the fields listed in this reference |
21
+ | `rawMissingReason`, `renderedMissingReason` | Both `null` when the corresponding observations exist |
22
+ | `rawFacts`, `renderedFacts` | `null` for the minimal import; preserve an existing matching raw fact snapshot if present. The importer extracts source text; hand-authored fact snapshots are unnecessary. |
23
+
24
+ Each observation contains `observationId`, `fallbackForObservationId`, `requestedUrl`, `finalUrl`, `observedAt`, `method`, `status`, `errorKind`, `headers`, `body`, `bodyHash` and `truncated`; optional `incompleteReason` is nullable. For the new successful render:
25
+
26
+ - Assign a new local unique `observationId` to this actual attempt; it is a record identity, not proof of retrieval. Set `fallbackForObservationId` to the preserved raw observation's ID, and `method` to `"rendered"`. The raw observation's fallback remains `null`.
27
+ - Use its own requested/final URLs, observed timestamp and integer 2xx status. Set `errorKind:null` only for the actual received HTTP response. Headers are a `Record<string,string>`: lowercase header-name keys and string values. Keep array/multivalue tool records separately rather than inventing combined values; use `{}` when no compatible headers were recorded.
28
+ - `body` is the exact full DOM HTML string. `bodyHash` is lowercase SHA-256 of its UTF-8 bytes, without trimming, reformatting or adding a newline. Set `truncated:false` and `incompleteReason:null` only when the tool returned the full capture without an incomplete/budget condition.
29
+
30
+ If an observation is absent, its missing-reason string explains why and its facts are null. That represents a gap, not a successful render: this importer requires complete rendered HTML/metadata. This recipe also requires the existing raw observation rather than synthesizing a missing raw view.
31
+
32
+ ## Assemble locally with Node's built-ins
33
+
34
+ The local `browser-capture.json` in this example contains the actual browser-returned fields described in [Acquire the observations](#acquire-the-observations); it is not a separate CLI input contract. Use the customer's actual paths and URL. No repository source, extra library or global setup is needed.
35
+
36
+ ```javascript
37
+ import { createHash, randomUUID } from "node:crypto";
38
+ import { readFileSync, writeFileSync } from "node:fs";
39
+
40
+ const corpus = JSON.parse(readFileSync("<corpus.json>", "utf8"));
41
+ const captured = JSON.parse(readFileSync("<browser-capture.json>", "utf8"));
42
+ const url = captured.requestedUrl; // Must be the exact saved inventory URL.
43
+ const page = corpus.pages.find((item) => item.url === url);
44
+ const previous = corpus.pageViews?.find((item) => item.url === url);
45
+ const raw = page?.observation.method === "rendered"
46
+ ? previous?.raw : page?.observation;
47
+ if (!raw || typeof captured.body !== "string" || !captured.finalUrl ||
48
+ !Number.isInteger(captured.status) || captured.status < 200 ||
49
+ captured.status >= 300 || !captured.observedAt) {
50
+ throw new Error("Keep the capture partial: current raw or browser metadata is missing.");
51
+ }
52
+ // Use this success recipe only after verifying full, untruncated browser output.
53
+ const sha256 = (value) => createHash("sha256").update(value, "utf8").digest("hex");
54
+ const normalized = new URL(url);
55
+ normalized.hash = "";
56
+ const headers = Object.fromEntries(Object.entries(captured.headers ?? {})
57
+ .filter(([, value]) => typeof value === "string")
58
+ .map(([key, value]) => [key.toLowerCase(), value]));
59
+ const matchingRawFacts = previous?.raw?.observationId === raw.observationId &&
60
+ previous?.raw?.bodyHash === raw.bodyHash ? previous.rawFacts : null;
61
+ const rendered = {
62
+ observationId: randomUUID(),
63
+ fallbackForObservationId: raw.observationId,
64
+ requestedUrl: url,
65
+ finalUrl: captured.finalUrl,
66
+ observedAt: captured.observedAt,
67
+ method: "rendered",
68
+ status: captured.status,
69
+ errorKind: null,
70
+ headers,
71
+ body: captured.body,
72
+ bodyHash: sha256(captured.body),
73
+ truncated: false,
74
+ incompleteReason: null,
75
+ };
76
+ const selected = {
77
+ pageId: `page:${sha256(normalized.href)}`,
78
+ url, raw, rendered,
79
+ rawMissingReason: null,
80
+ renderedMissingReason: null,
81
+ rawFacts: matchingRawFacts ?? null,
82
+ renderedFacts: null,
83
+ };
84
+ writeFileSync("<new-selected-page.json>", JSON.stringify(selected), {
85
+ flag: "wx", mode: 0o600,
86
+ });
87
+ ```
88
+
89
+ Run the resulting local file once through the installed CLI:
90
+
91
+ ```bash
92
+ obi research capture --input <new-selected-page.json> --checkpoint <checkpoint.json> --output <corpus.json> --json
93
+ ```
94
+
95
+ The command selects the rendered source, recomputes extraction/hash and preserves the raw counterpart in `pageViews`. Identical replay reuses the exact input file/IDs/timestamp; regenerating the file creates another attempt, not an identical replay. A new render must not predate its raw observation and must be strictly later than an existing rendered observation, unless the entire input is an identical replay. A new local ID does not turn old navigation metadata into a new read. A changed raw observation or stale render fails; reread current files and recapture rather than rewriting their history. Input is limited to 32 MiB, each HTML observation to 3 MiB, and the local corpus to 512 MiB. Unsafe/cross-site URLs or an absent selected inventory URL fail rather than widening scope.
96
+
97
+ Read the returned revision, inventory and coverage: new links may leave work pending. Then follow [Business submission and retrieval](host-work.md#submission-and-capture-provenance). A successful local import is not saved Business evidence until submission succeeds, and a Readiness rendering difference alone is not a persisted downstream claim. Retrieve the saved claim/source before using it in questions or strategies.
98
+
99
+ The Node operations use the standard [crypto](https://nodejs.org/api/crypto.html), [URL](https://nodejs.org/api/url.html) and [filesystem](https://nodejs.org/api/fs.html) APIs. Capture identities/hashes prove consistency, not independent fetch authenticity.
@@ -0,0 +1,70 @@
1
+ # Recording and revisiting actual outcomes
2
+
3
+ Use only outcome operations advertised by `capabilities`. Public-company
4
+ evaluations are read-only for outcomes: researching a company does not prove
5
+ the operator implemented changes there. Actual implementation observations
6
+ belong to the connected business owner's account.
7
+
8
+ ## Read before a write or conclusion
9
+
10
+ ```bash
11
+ obi harness outcomes --task-id <saved-task-UUID> --json
12
+ obi harness outcomes --task-id <saved-task-UUID> --cursor <nextCursor> --json
13
+ ```
14
+
15
+ Preserve the same task and any selected evaluation scope on every read. The
16
+ response supplies total, truncated and nextCursor: follow all pages needed for
17
+ the decision; a bounded strategy-context preview is not the whole journal.
18
+
19
+ Read after a lost write response before retrying. Reuse the exact file and
20
+ replay UUID for an identical observation; a changed payload needs a new UUID.
21
+ An unknown task or missing server capability requires a specific recovery, not
22
+ a hosted fallback or an invented successful save.
23
+
24
+ ## Implementation, publication and follow-up are separate
25
+
26
+ After the customer confirms what actually changed and when, save a local
27
+ implementation record and submit it with `obi harness observe --input <file>
28
+ --json`. Its shape is:
29
+
30
+ ```json
31
+ {
32
+ "schemaVersion": 1,
33
+ "taskId": "<saved-task-UUID>",
34
+ "replayKey": "<new-UUID>",
35
+ "kind": "implementation",
36
+ "implementedAt": "<confirmed-ISO-timestamp>",
37
+ "ownerConfirmed": true,
38
+ "summary": "<customer-confirmed change>",
39
+ "targetUrl": "<actual same-site URL, or null>"
40
+ }
41
+ ```
42
+
43
+ The returned observation ID binds later observations. An approved proposal or
44
+ local draft is not implementation. Save only the minimum authorized customer
45
+ material; private context does not become a cross-customer learning example.
46
+
47
+ Publication uses `kind: "publication"`, the same task, a new replay UUID,
48
+ `implementationEventId`, `actualUrl`, `confirmedAt`, and a fresh acquired
49
+ `pageObservation` plus matching `parsedPage`. Use the public-page acquisition
50
+ contract from [host work](host-work.md); pass the actual complete observation
51
+ and parsed page, not a model-written claim that the URL is live. Include
52
+ `artifactRevisionVersion` and `expectedText` only for a real saved revision;
53
+ otherwise both are null. Do not invent an artifact revision for host-produced
54
+ work. The server validates the record and distinguishes host-supplied HTTP
55
+ evidence from indexing, rendered visibility and actual provider access.
56
+
57
+ A follow-up uses `kind: "follow_up"`, the same task and
58
+ `implementationEventId`, a new replay UUID and the UUID of a later server-stored
59
+ `scanRunId`. The server derives the comparison from recorded measurements.
60
+ Historical actions can be followed after later scans. Incompatible question
61
+ versions, markets, engines or timing return a non-comparable or insufficient
62
+ evidence result rather than a made-up lift. The observation does not start a
63
+ new scan.
64
+
65
+ Before presenting an effect, inspect the saved comparison, missing cells,
66
+ concurrent changes and baseline. Separate a client report, verified HTTP
67
+ content, a measured answer change and a causal explanation. Saved observations
68
+ inform the next strategy context; general method promotion remains reviewed
69
+ and evaluated separately. Recording observations never executes a task,
70
+ publishes content, changes its paid status or consumes credits.
@@ -0,0 +1,89 @@
1
+ # Buying paid packs
2
+
3
+ Read when the customer wants to buy packs or change the packs or engines of a paid plan, and before proposing a change yourself. Buying happens in this conversation, where the customer reviews the questions each pack will measure; the `/billing` page shows the plan and keeps cards, invoices and cancellation. Every price you show comes from the server's quote response.
4
+
5
+ Only the owner of the connected business account buys, with a verified email; a public-company evaluation case cannot.
6
+
7
+ ## What the customer decides
8
+
9
+ | Decision | Where it comes from |
10
+ |---|---|
11
+ | How many packs | Each pack measures 25 reviewed questions; the customer picks the number from the buying situations they want covered. |
12
+ | Optional engines | Claude and Perplexity each measure 1 to 10 distinct questions per pack, chosen among those marked `buyingEligible`. Propose only genuine buying questions, however few. |
13
+ | The questions | The full reviewed set, written as in [questions](questions.md#writing-the-questions). |
14
+ | The monthly amount | The quote's `monthlyAmountCents`, shown before any payment step. |
15
+
16
+ ## From review to active coverage
17
+
18
+ 1. **Review the portfolio.** Run `obi harness show --stage questions --packs <N> --json` and submit exactly `data.context.purchaseReview.questionCount` questions with `result.intendedPacks`, as [host work](host-work.md) describes. The customer reviews them; active coverage stays unchanged until payment. Then `obi questions show --json` gives `data.candidate.id` and `data.candidate.version`, which identify the reviewed set.
19
+ 2. **Quote.** Write one local file and keep it for every later step, copying the actual IDs and version:
20
+
21
+ ```json
22
+ {
23
+ "quoteId": "<new UUID kept for this exact basket>",
24
+ "basket": {
25
+ "catalogVersion": "obi-cli-dollar-v2",
26
+ "packs": 1,
27
+ "claude": true,
28
+ "perplexity": false,
29
+ "candidateSetId": "<data.candidate.id>",
30
+ "candidateVersion": 3,
31
+ "coreQuestionIds": ["<every question ID of the reviewed set: 25 per pack>"],
32
+ "claudeQuestionIds": ["<1 to 10 buying question IDs per pack>"],
33
+ "perplexityQuestionIds": []
34
+ }
35
+ }
36
+ ```
37
+
38
+ An engine set to `false` has an empty ID list. Run `obi harness purchase quote --input <file> --json`: it validates the basket against the current reviewed set and charges nothing. Show the customer the monthly amount, the packs, the engines and the questions each engine measures. Keep `scanBudget` out of the price, as [questions](questions.md) explains.
39
+ 3. **Prepare checkout, on the customer's explicit yes to that quote.** Run `obi harness purchase prepare --input <same file> --confirm-checkout --json`. It freezes the quote for 30 minutes (`expiresAt`) and opens `/billing` in the customer's browser (`browserUrl`; when `browserOpened` is false, give them the link). They open checkout there within that window and pay. Paid coverage starts only after verified payment.
40
+ 4. **Follow and resume.** `obi harness purchase status --json` reports `activationState`:
41
+
42
+ | `activationState` | Meaning and next step |
43
+ |---|---|
44
+ | `awaiting_payment` | Payment is not recorded yet; the customer finishes it on `/billing`. |
45
+ | `scope_activation_required` | Payment is recorded and the reviewed questions are not active yet. With `nextAction.kind` `resume_quoted_scope`, write a file holding only `{"quoteId": "<same UUID>"}` and run `obi harness purchase resume --input <that file> --json`. With `review_quoted_dependencies`, show `blockedReasons`, bring the Business review or the questions current with the customer, then resume. |
46
+ | `active` | The paid scope is active, and Obi starts a scan of the whole paid portfolio by itself within the hour: show the new coverage from `obi harness coverage --json` and that scan's state from `obi questions show --json`. |
47
+
48
+ ## Changing a paid plan
49
+
50
+ A customer with paid packs changes them with `obi harness purchase change`; a first purchase uses the steps above.
51
+
52
+ 1. Review the new portfolio as in step 1, with the new pack count: write the added questions for an increase; for a reduction, keep the questions with the strongest evidence and priorities. The customer reviews the full set.
53
+ 2. Write the quote file of step 2 for the new plan and run `obi harness purchase change --input <file> --json`. The preview charges nothing:
54
+
55
+ | `kind` | What to show |
56
+ |---|---|
57
+ | `increase` | `amountDueCents`, charged now to the card on file and prorated to the renewal, then the new `quote.monthlyAmountCents`. |
58
+ | `reduction` | No charge now; the new monthly amount from `effectiveAt`, the renewal, with the current coverage kept until then. |
59
+
60
+ 3. On the customer's explicit yes to that amount, before `expiresAt`, run the same command with `--confirm-change`. Its `outcome`:
61
+
62
+ | `outcome` | Next step |
63
+ |---|---|
64
+ | `applied` | The new questions activate once the payment is recorded: follow `purchase status` and resume as in step 4. |
65
+ | `scheduled` | The reduction applies at `effectiveAt`. |
66
+ | `payment_confirmation_required` | Their bank asks for a confirmation on `/billing` (`browserUrl`); the increase applies once it is confirmed. |
67
+ | `preview_changed` | Nothing was charged: show the new `amountDueCents` and ask again. |
68
+
69
+ A change that adds and removes at once (`PLAN_CHANGE_TWO_STEPS`) runs in two steps, each with its own yes: the increase first, then the reduction once the increase is applied. Only Obi support undoes a scheduled reduction: give the customer support@hireobi.com.
70
+
71
+ ## Proposing a change yourself
72
+
73
+ When evidence shows a gap the plan does not measure, such as a buying situation outside the reviewed questions or a rival cited on questions the plan leaves out, propose the change once, with that evidence and the price from a quote or preview. After a no, raise it again only on new evidence.
74
+
75
+ ## What waits for the customer
76
+
77
+ - Checkout preparation: their explicit yes to the displayed quote.
78
+ - A plan change: their explicit yes to the previewed amount and date.
79
+ - Payment: made by the customer on `/billing`; card details stay in that page.
80
+ - Any other basket: changed packs, engines or questions get a new quote, a new UUID and their review.
81
+
82
+ ## Recovering
83
+
84
+ - A lost response on `prepare` or `resume`: read `purchase status` first, then retry the exact same file and UUID; `resume` never starts another checkout.
85
+ - A refusal because the reviewed questions changed before `prepare`: read `questions show` again and quote the current set with a new UUID.
86
+ - After `prepare`, the open checkout keeps its quote, and `prepare` with another basket or UUID is refused until the customer closes that checkout in their browser or its 30 minutes pass. Then read `questions show` again and quote the current set with a new UUID.
87
+ - An existing paid subscription makes `prepare` fail with `PURCHASE_CONFLICT`: change its plan with `purchase change`.
88
+ - A `PLAN_CHANGE_*` refusal carries its next step in `nextAction`: an expired preview needs a new quote UUID, an uncertain outcome needs `purchase status` before any retry, and a refused payment leaves the plan unchanged.
89
+ - `PURCHASE_CONFLICT` on `resume`: keep the quote UUID and any recorded payment, read status again, confirm the current Business review and Readiness, and review the questions before retrying.
@@ -0,0 +1,40 @@
1
+ # Buyer questions, scan and priorities
2
+
3
+ After the business review is confirmed, inspect `obi harness show --stage questions --json`. Show the supported measurement markets and confirmed buyer audiences from its work context. Ask the customer to choose one market and one primary audience; a suggested market is guidance, not consent or a default. Prepare the displayed coverage count in the host and save it with the versioned `harness submit --stage questions --input <file> --json` path described in [host work](host-work.md). `obi questions show --json` reads the accepted set and its preparation state. A queued or running state means wait and inspect again after meaningful progress; do not announce that questions already exist.
4
+
5
+ ## Writing the questions
6
+
7
+ A question is the prompt a real buyer types into ChatGPT, Claude, Gemini or Perplexity while deciding what to buy. The scan measures what those engines answer to it, so a generic question measures a market nobody is in. Follow `data.context.method` and the customer's saved memory (`obi memory list --json`) in this order:
8
+
9
+ 1. Read everything Obi learned before writing a word: run the command in `data.context.evidence.read` (one file with every persisted fact: offer, prices, use cases, integrations, objections, proof, competitor positioning, quoted buyer vocabulary). Split the file among subagents when the host offers them; each returns the facts that matter for one awareness stage. When `data.context.evidence` is null (a business read before the harness existed), the facts are the review's own citations in `data.allowedSourceRefs`: read them through `business show` and cite those.
10
+ 2. Write from the selected buyer's seat: their job, the situation that triggers the search, the constraint they state (budget, country, stack, team size, compliance), in the words the pages quote from them. Cite the claim IDs you used in `sourceRefs`; the audience claim alone is refused.
11
+ 3. Cover the awareness stages across the set: unaware of the category, aware of the problem, comparing solution types, evaluating this kind of product, choosing between named options. Spread the six intents.
12
+ 4. Shape each question like a chat prompt: first person, one ask, 8 to 30 words, a concrete situation, no adjectives copied from marketing pages. "Best AI gateway?" is a search query; "We are three developers shipping a Django app and need one API for several model providers with EU hosting, what should we use?" is a buyer prompt.
13
+ 5. Keep most questions unbranded. The server refuses a set where more than the published share (`data.context.method.limits.brandedShare`) name the company or a rival; comparison and alternatives intents use that allowance.
14
+ 6. Start most questions from the buyer's broad problem, where the company competes with other kinds of solutions. A buyer who already knows the company's niche wording is already looking for it. Use the deduced angles the customer confirmed (for example, migrating away from a named rival).
15
+ 7. Leave out what the buyer does not search for: the product's form factor (CLI, API, MCP) unless the buyer's stated job requires it, and the country unless it changes the answer (compliance, local data, currency). The scan already runs in the selected market.
16
+ 8. Name a specific tool only when the evidence, the customer's harness or the customer's answer supports it (for example, their CRM).
17
+
18
+ ### Buyer profiles and the cold/context pairs
19
+
20
+ A real buyer asks with their chat memory on: the engine already knows their tools and constraints. Measure that effect with pairs:
21
+
22
+ - Write three to five profiles from the personas of the primary ICP ([ICP and personas](business-judgment.md#icp-and-personas)). A profile is one first-person paragraph that describes one person as their assistant's memory would know them: role, company, the tools that matter for this target (one per slot, never two), and their situation or constraint. Vary the profiles where real buyers vary. "As a GTM engineer" alone is not a profile. Cite each profile's facts in its `sourceRefs`: published claim IDs, or the IDs of customer memory entries (`obi memory list --json`) that recorded the customer's confirmed answers.
23
+ - For each pair, set `arm: "cold"` on the bare question and `arm: "context"` on its twin, with the same `pairId`. The twin's `text` is the profile text, one space, then the cold text, unchanged. Attach the profile (`id`, `text`, `sourceRefs`) to the context question; the cold question has `profile: null`.
24
+ - Spread the profiles across pairs so that each profile covers several intents. Every question of a paired set belongs to a pair, so the count must be even.
25
+ - A set must carry three to five distinct profiles; the server refuses fewer or more. Changing the layout of a paired set (adding, dropping or re-planning pairs or profiles) is reclassified as a new portfolio, not an edit. A wording edit of an existing pair keeps its arm, pair ID and profile ID.
26
+ - When reading results, compare each pair's two arms on the same engine: the brands named, the company's presence and the rivals' presence. The difference is what the buyer's memory changes. The `arm`, `pairId` and `profile` of each question are in `harness show --stage questions` under `context.currentCandidate.questions`.
27
+
28
+ Show the customer five representative questions with the fact each one came from, then the full set on request. The customer judges the voice; the server only checks that each question cites a fact beyond the audience and that the branded share holds.
29
+
30
+ When Business corrections change the buyer or served market, reread its accepted review and fresh questions context before drafting. Resolve the selected measurement market and primary audience against that context, retaining corrected source references. Preserve unaffected accepted work; old questions remain visibly based on the earlier Business version until their replacement is accepted.
31
+
32
+ Show the actual saved count, mix and representative wording, and offer the full set. The customer can correct a question in ordinary language. For the harness alpha, read the latest questions work and edit the full `context.currentCandidate.questions` array using the customer's wording; preserve its IDs, intents, facets, selected market, primary audience and source references. Submit it as a new versioned questions result. Read `questions show` after acceptance; a failed or stale submission leaves the prior version distinct. The older `questions edit` route is unavailable for this grant because it launches server reasoning.
33
+
34
+ For a paid pack, Obi starts this scan by itself within the hour after the paid scope becomes active, by a purchase or a paid increase, and starts a failed one again: read `questions show` for its state instead of asking for an authorization; `questions proceed` would only replay it. When `questions show` still shows no scan of the paid scope an hour after it became active, and before any other `questions proceed`, read `harness coverage` and show the selected question and engine coverage, its cell count and whether `budgetQuote.completeCoverageAuthorized` is true. The USD values in `scanBudget` and `budgetQuote` are internal supplier spend limits, not a customer price. If full coverage is not authorized, obtain a reviewed budget grant before proceeding; a question edit cannot fix that error. Ask for the one scan authorization. Only an explicit yes runs `questions proceed --candidate-version <displayed version> --confirm-scan --json`. A retry after timeout begins with `questions show`; never replay a paid scan blindly. A customer subset does not mean every question was measured on every optional engine. A founder evaluation may have a separate authorized five-engine plan; use the displayed plan rather than an assumed count.
35
+
36
+ `questions results --json` contains independently measured answers, timestamps, engines and citations. Show completed and missing cells plainly.
37
+ It reads the latest completed scan, named in `run`; `runs` lists the customer's scans newest first, and `questions results --run <id> --json` (or `--run first`) reads one of them; a long scan pages, and `evidencePreview.fullEvidenceCommand` names the `--from` command for the same run's next questions. Compare two weeks only on cells both runs scored; an engine whose `status` is `partial` or `failed`, and each question's `gaps`, are planned cells without a scored answer: report them as missing evidence and leave them out of mention and citation rates. Acquire relevant cited or retrieved public sources before judging whether a factual claim is outdated. The host's answer to a question is a suggestion, never a measured result.
38
+ `aggregates` gives that run's share of voice, leaderboard and most cited domains from the scorer: each percentage is a share of its `denominator.answersN` answers, which leaves out refusals and the `ungroundedN` answers given without a web search. Quote the denominator with every percentage, and when comparing weeks quote both, since they differ.
39
+
40
+ After a completed scan, inspect `obi harness show --stage business --json` again. If it offers new scoped work after confirmation, acquire the relevant scan-cited media and any requested own or rival refresh; submit sourced observations before reasoning over strategies. This work does not reopen the already confirmed Business review. Then inspect `obi harness show --stage strategies --json`. Compare measured gaps with the saved business facts, acquired own/rival/media page facts, Readiness findings and user feedback. `questions results` may display a bounded `evidencePreview`; its listed questions and 500-character excerpts are not the full answer set. Use `harness evidence --work-id <UUID> --kind claims --query <gap> --json` for persisted facts, sections for quote context and `--kind measurements` for the pinned selected scan. Page through `nextCursor` and reconstruct the full answer and source references from their offsets before claiming a complete trace. Do not treat the compact work context as the whole corpus. Save supported ranked priorities with `harness submit --stage strategies --input <file> --json`, then read `obi strategies --json`. Rank by urgency × buyer importance first, then evidence, speed and effort; the three selected actions are the ones the customer should start this week, each with the observation it rests on (`evidenceRefs`: a measured question, a readiness failure, a page fact) and, when present, its `evidenceLevel`: say the level, its basis and how the customer verifies the result, and never call an action proven. Show the three, then the `considered` list the server returns: what was weighed and why it ranks lower. When the customer asks for more, present the next candidates from that list with their evidence; when the list is exhausted, say so and name the observation (a new scan, an implemented action, a widened reading scope) that would produce new candidates. If a strategy work unit is still awaiting the harness, report that state and resume it when the customer returns. If evidence supports fewer than three distinct actions, explain the gap rather than padding the list. Keep rejected actions in the considered set and change them only when new evidence warrants it. A recommendation is not a published asset; local briefs or drafts need customer review.