<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Princy Gandhi]]></title><description><![CDATA[Storytelling by algorithms. ]]></description><link>https://princygandhi.substack.com</link><image><url>https://substackcdn.com/image/fetch/$s_!lBbm!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7ad78b29-d9b2-48af-9357-cae27fd3f148_794x794.jpeg</url><title>Princy Gandhi</title><link>https://princygandhi.substack.com</link></image><generator>Substack</generator><lastBuildDate>Mon, 03 Aug 2026 05:25:09 GMT</lastBuildDate><atom:link href="https://princygandhi.substack.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Princy Gandhi]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[princygandhi@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[princygandhi@substack.com]]></itunes:email><itunes:name><![CDATA[Princy Gandhi]]></itunes:name></itunes:owner><itunes:author><![CDATA[Princy Gandhi]]></itunes:author><googleplay:owner><![CDATA[princygandhi@substack.com]]></googleplay:owner><googleplay:email><![CDATA[princygandhi@substack.com]]></googleplay:email><googleplay:author><![CDATA[Princy Gandhi]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Your AI visibility score says a brand showed up, not that it should have]]></title><description><![CDATA[Measure whether AI answers recommend the right brands using Amazon's ESCI dataset]]></description><link>https://princygandhi.substack.com/p/your-ai-visibility-score-says-a-brand</link><guid isPermaLink="false">https://princygandhi.substack.com/p/your-ai-visibility-score-says-a-brand</guid><dc:creator><![CDATA[Princy Gandhi]]></dc:creator><pubDate>Sun, 02 Aug 2026 16:59:17 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/750e3fa4-66db-439d-a025-5c0ac1581699_1940x670.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Answer engine optimization tools report brand visibility in AI answers: how often a brand is named, its share of voice, and its position. These metrics show whether a brand appears. They do not show whether it should have. A model can name a brand that does not fit the query, or omit one that does, and a visibility score counts the mention either way.</p><p>In this post, we focus on measuring the second question. I built a ground-truth benchmark from Amazon&#8217;s Shopping Queries Dataset (ESCI), where human raters labelled which brands are relevant to each query, and compare share of voice against agreement with those judgements. The output is a dashboard that ranks brands both ways and surfaces where the two disagree.</p><p>The dashboard is live at <a href="https://princy2310.github.io/esci-brand-study/">https://princy2310.github.io/esci-brand-study/</a> and the full source is in the <a href="https://github.com/princy2310/esci-brand-study">esci-brand-study GitHub repository</a>.</p><h3><strong>Solution overview</strong></h3><p>The project is split into two parts: a Python step that samples ESCI and derives aggregates, and a Next.js dashboard that renders them.</p><ol><li><p><code>precompute.py</code> pulls rows from the ESCI dataset through the Hugging Face datasets-server API, filtered to the US locale.</p></li><li><p>It cleans the brand field, rolls product-level relevance judgements up to brands, and derives the competitive sets, share-of-voice figures, and substitute pairs.</p></li><li><p>It writes the aggregates to a single JSON file, which I commit to the repository.</p></li><li><p>The dashboard reads that file at build time, validates it against a Zod schema, and renders four views.</p></li></ol><p>We commit the aggregates rather than fetch them at runtime because the datasets-server API rate-limits anonymous access to 500 requests per 5-minute window. Committing the output also keeps the numbers stable between page loads.</p><h3><strong>Why ESCI</strong></h3><p>This exercise needs a dataset where brands are labelled and relevance is judged by people, and ESCI (Reddy et al., arXiv:2206.06588, Apache-2.0) is the one that fit. It contains around 130,000 shopper queries and 2.6 million relevance judgements. Each judgement is a human rating of how well a product matches a query, using four labels:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!DW4P!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!DW4P!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 424w, https://substackcdn.com/image/fetch/$s_!DW4P!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 848w, https://substackcdn.com/image/fetch/$s_!DW4P!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 1272w, https://substackcdn.com/image/fetch/$s_!DW4P!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!DW4P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png" width="483" height="197" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:197,&quot;width&quot;:483,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:26691,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://princygandhi.substack.com/i/209515880?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!DW4P!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 424w, https://substackcdn.com/image/fetch/$s_!DW4P!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 848w, https://substackcdn.com/image/fetch/$s_!DW4P!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 1272w, https://substackcdn.com/image/fetch/$s_!DW4P!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F87827fa8-98ca-4250-80bf-9e0a67563aea_483x197.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>Every product carries a brand, so the judgements roll up from products to brands. For the query <code>revent 80 cfm</code>, raters judged Aero Pure, Broan-NuTone, Delta Electronics, Homewerks and Panasonic. That gives me a competitive set with a human verdict on each brand.</p><p>Sampling the US slice: 10,746 rows across 2,770 queries. Of those, 998 are competitive, meaning more than one brand was judged against the same query. These competitive sets are my unit of analysis.</p><h2><strong>The two metrics</strong></h2><p>Computing two numbers per brand.</p><ul><li><p><strong>Share of voice</strong> is the share of competitive queries a brand appears in. This is the presence metric the AEO tools report.</p></li><li><p><strong>Agreement</strong> is the share of a brand&#8217;s judgements that raters labelled Exact. This is the correctness metric the ground truth makes possible.</p></li></ul><p>Ranking the brands by each produces two orderings. In this sample they barely relate: the Spearman rank correlation between them stays within 0.12 of zero at every threshold I tested. Share of voice does not predict agreement.</p><p>The individual cases are clearer than the correlation. These brands rank high on visibility and low on agreement:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!CcCX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!CcCX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 424w, https://substackcdn.com/image/fetch/$s_!CcCX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 848w, https://substackcdn.com/image/fetch/$s_!CcCX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 1272w, https://substackcdn.com/image/fetch/$s_!CcCX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!CcCX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png" width="498" height="158" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:158,&quot;width&quot;:498,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:18212,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://princygandhi.substack.com/i/209515880?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!CcCX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 424w, https://substackcdn.com/image/fetch/$s_!CcCX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 848w, https://substackcdn.com/image/fetch/$s_!CcCX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 1272w, https://substackcdn.com/image/fetch/$s_!CcCX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F779221fe-fa59-47db-88b2-e8bafbd59dc0_498x158.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>A visibility report places these brands near the top. A correctness report places them near the bottom.</p><h2><strong>Why use query floor</strong></h2><p>Agreement is not comparable across brands of different breadth. A brand judged against a single query cannot be wrong on average, so it scores 100% agreement mechanically. In this sample, brands appearing on one query average 79.4% agreement, with half at a perfect 100%, against 70.2% for brands on nine or more queries.</p><p>When left unfiltered, single-query brands filled the top of the agreement ranking and pushed the correlation slightly negative on exposure alone. So we require a brand to appear against at least five distinct queries before it enters the ranking, which removes the effect. The full threshold sweep ships in the data.</p><h2><strong>Cleaning the brand field</strong></h2><p>The <code>product_brand</code> field needed cleaning before the aggregates held up. <code>precompute.py</code> handles three cases:</p><ul><li><p><strong>Case variants.</strong> <code>1MORE</code> and <code>1More</code> are one company. Left split, they divide the judgements and understate agreement. I collapse them on a case-folded key.</p></li><li><p><strong>Product titles.</strong> Some sellers fill the field with keyword-stuffed descriptions rather than brand names. I flag these and exclude them by default.</p></li><li><p><strong>Unresolved aliases.</strong> A non-Latin name with a bracketed romanisation folds cleanly, so <code>&#12467;&#12540;&#12523;&#12510;&#12531;(Coleman)</code> becomes <code>Coleman</code>. A bare one does not, so I exclude <code>&#12461;&#12516;&#12494;&#12531;</code> for Canon from the substitute graph, where it would otherwise appear as a brand paired with itself.</p></li></ul><p>Of 3,928 brands in the sample, 602 appear against three or more queries. The rest are single-appearance marketplace sellers.</p><h2><strong>Scoring a model against the benchmark</strong></h2><p>With the ground truth in place, I scored a model against it. I put 120 competitive queries to Claude Sonnet 4.5 with no tools and no web search, so the answers reflect what the model itself associates with each query, then matched the brands it named to the human judgements.</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!U6Ja!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!U6Ja!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 424w, https://substackcdn.com/image/fetch/$s_!U6Ja!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 848w, https://substackcdn.com/image/fetch/$s_!U6Ja!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 1272w, https://substackcdn.com/image/fetch/$s_!U6Ja!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!U6Ja!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png" width="449" height="161" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:161,&quot;width&quot;:449,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:22790,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://princygandhi.substack.com/i/209515880?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!U6Ja!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 424w, https://substackcdn.com/image/fetch/$s_!U6Ja!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 848w, https://substackcdn.com/image/fetch/$s_!U6Ja!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 1272w, https://substackcdn.com/image/fetch/$s_!U6Ja!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6571d09e-ba63-4cdb-a6ea-682c77cd9925_449x161.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><p>The headline is the last row. When the model names a brand that ESCI also judged, it is Exact 83% of the time. It is accurate on the overlap. But it recovers only a quarter of the full Exact set, and it names the single leading brand 40% of the time, so it is incomplete against the whole picture.</p><p>One number needs care. The model names about 11 brands per query, and ESCI had judged only about 2 of them, because ESCI only labelled products that appeared in Amazon search results. So 82% of the model&#8217;s named brands are not in ESCI&#8217;s judged set for that query. That is mostly real brands outside ESCI&#8217;s coverage, not inventions, and it is why raw precision across all named brands is 14%. We report precision over the brands ESCI actually judged instead, which removes the coverage gap. Reading the off-corpus rate as a hallucination rate would be wrong.</p><p>The full 120-query run cost about 12 cents.</p><h2><strong>Prerequisites</strong></h2><ul><li><p>Node 20 or later</p></li><li><p>Python 3.10 or later, only if you want to regenerate the aggregates or run the model scoring</p></li><li><p>An Anthropic API key, only for the model scoring</p></li></ul><h2><strong>Walkthrough</strong></h2><p>Clone the repository and run the dashboard:</p><pre><code><code>git clone https://github.com/princy2310/esci-brand-study.git
cd esci-brand-study/dashboard
npm install
npm run dev</code></code></pre><p>Open http://localhost:3000. The dashboard loads the committed aggregates, so it runs without any dataset access or API keys.</p><p>To regenerate the aggregates from ESCI:</p><pre><code><code>cd ../scripts
python3 precompute.py --rows 20000 --locale us --out ../dashboard/public/data</code></code></pre><p>The sampling backs off through the rate limit, so a full run takes several minutes. I also wrote <code>verify.py</code>, which checks the output against the source and confirms the figures quoted here.</p><p>To run the model scoring, put an Anthropic key in <code>.env</code> and run the scorer:</p><pre><code><code>echo "ANTHROPIC_API_KEY=sk-ant-..." &gt; .env
python3 score_model.py --limit 120</code></code></pre><p>It checkpoints after every query, so an interrupted run resumes without repeating calls.</p><h2><strong>Limitations</strong></h2><ul><li><p><strong>Temporal.</strong> ESCI reflects Amazon search behaviour up to its 2022 publication. A model naming a product released after 2022 is hitting a gap in the dataset, not making an error. This is the caveat most likely to produce a wrong conclusion.</p></li><li><p><strong>Relevance is not market share.</strong> Exact means a product matched the query intent, not that it sells well. A brand can be relevant and obscure.</p></li><li><p><strong>Amazon-shaped intent.</strong> These are product searches, not open-ended advice questions, so the findings apply to purchase-adjacent prompts.</p></li><li><p><strong>No category column.</strong> ESCI labels relevance per query and product, so the unit of analysis is the query.</p></li></ul><h2><strong>Conclusion</strong></h2><p>We built a correctness baseline for AI brand recommendations from human relevance judgements, showed that share of voice and agreement rank brands differently on the same data, and scored a model against it. Visibility tools answer whether a brand appears; this answers whether it belonged. Claude Sonnet 4.5 is accurate when it names a judged brand but recovers only a quarter of the human-relevant set, and finds the leading brand 40% of the time.</p>]]></content:encoded></item><item><title><![CDATA[How to automate research using Market Intel Agent]]></title><description><![CDATA[Product managers spend a significant part of every week gathering competitive signal by hand.]]></description><link>https://princygandhi.substack.com/p/how-to-automate-research-using-market</link><guid isPermaLink="false">https://princygandhi.substack.com/p/how-to-automate-research-using-market</guid><dc:creator><![CDATA[Princy Gandhi]]></dc:creator><pubDate>Sat, 01 Aug 2026 23:17:41 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/A-qOSpISBto" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>Product managers spend a significant part of every week gathering competitive signal by hand. They check Reddit for user complaints, scan Hacker News for competitor launches, search Stack Overflow to see whether developers are struggling with an API, and hunt for the analyst report someone mentioned in a meeting. The work is valuable, but it is manual, inconsistent, and rarely reproducible. Nobody can say with confidence what changed since last Tuesday.</span></p><p><span>In this post, I will show you how to deploy Market Intel Agent, an autonomous research agent that runs this workflow on a schedule. The agent searches across community, developer, and web sources for competitor and market signal, uses Anthropic&#8217;s Claude Sonnet 4 on Amazon Bedrock to synthesize findings, and delivers a formatted digest by email through Amazon SES. It is built with the Strands Agents SDK, runs on Amazon Bedrock AgentCore, scales to zero when idle, and supports both on-demand deep dives and daily incremental reports.</span></p><div id="youtube2-A-qOSpISBto" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;A-qOSpISBto&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/A-qOSpISBto?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><span>The full source is available in the </span><a href="https://github.com/princy2310/market-intel-agent"><span>market-intel-agent GitHub repository</span></a><span>.</span></p><p><strong><span>Solution overview</span></strong></p><p><span>The solution has three entry points that converge on the same research agent: an on-demand request from the web UI, a scheduled daily run, and a direct invocation for testing.</span></p><p><span>The following describes the request flow:</span></p><ol><li><p><span>A user opens the web UI and submits a product name, focus areas such as adoption or security, and optionally a list of competitors to compare.</span></p></li><li><p><span>Amazon API Gateway routes the request to an AWS Lambda function (</span><code>lambda_api</code><span>), which writes the subscription to the </span><code>MarketIntelSubscriptions</code><span> Amazon DynamoDB table and checks Amazon SES verification status for the supplied email address. If the address is unverified, the function triggers a verification email.</span></p></li><li><p><span>When the user chooses </span><strong><span>Send First Report Now</span></strong><span>, the API function invokes the </span><code>MarketIntelWorker</code><span> Lambda function asynchronously and returns immediately. The agent takes one to two minutes to run, which exceeds a reasonable synchronous request timeout, so the API does not wait for it.</span></p></li><li><p><span>For scheduled delivery, an Amazon EventBridge rule fires daily at 08:00 UTC and invokes </span><code>lambda_trigger</code><span>. That function reads active subscriptions from DynamoDB and invokes the agent once per subscription in daily mode.</span></p></li><li><p><span>The agent runs on Amazon Bedrock </span><code>AgentCore</code><span>, invoked through the bedrock-agentcore API (</span><code>invoke_agent_runtime</code><span>). It executes a four-step research pipeline, calling web research tools to gather signal and Claude Sonnet 4 on Amazon Bedrock to plan and synthesize.</span></p></li><li><p><span>The agent composes an HTML digest and sends it through Amazon SES to the subscriber.</span></p></li></ol><p><span>Because every compute component is either Lambda or an AgentCore runtime, there is no always-on infrastructure. The solution costs nothing while idle.</span></p><p><span>Note that the repository provisions the DynamoDB table and EventBridge rule through a setup script, but the Lambda functions, API Gateway endpoint, and the EventBridge target wiring are deployed separately. The walkthrough covers this ordering.</span></p><p><strong><span>How the research pipeline works</span></strong></p><p><span>The agent (agent.py) does not issue a single &#8220;research my competitors&#8221; prompt. It runs four explicit stages, which keeps gathering separate from synthesis:</span></p><p><strong><span>Step 1, Discover.</span></strong><span> Calls </span><code>search_competitors_and_market </code>and<code> search_news_and_announcements</code><span> to find competitor launches, market reports, and analyst coverage.</span></p><p><strong><span>Step 2, Listen.</span></strong><span> Calls </span><code>search_community_sentiment, search_pain_points, </code>and<code> search_security_and_conferences</code><span> to surface what users are actually saying, in their own language.</span></p><p><strong><span>Step 3, Go deeper.</span></strong><span> Uses fetch_page_content to read two or three promising pages in full. Search snippets indicate that a page is relevant without revealing what it says, so this step is what produces direct quotes and specific figures rather than paraphrase.</span></p><p><strong><span>Step 4, Compose and send.</span></strong><span> Builds the HTML digest and delivers it through </span><code>send_digest_email</code><span>.</span></p><p><span>The agent supports two modes. </span><strong><span>Full analysis</span></strong><span> performs a comprehensive pass across all sources, and is appropriate when you first start tracking a product. </span><strong><span>Daily mode</span></strong><span> reports only what changed in the previous 24 hours, which is what makes a recurring digest worth opening.</span></p><p><strong><span>Multi-source research with normalized recency</span></strong></p><p><span>The research layer (tools/web_research.py) queries community, developer, and web sources. Each exposes different date semantics, so the tool applies per-source filtering rather than a single global window:</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!jOLN!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!jOLN!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 424w, https://substackcdn.com/image/fetch/$s_!jOLN!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 848w, https://substackcdn.com/image/fetch/$s_!jOLN!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 1272w, https://substackcdn.com/image/fetch/$s_!jOLN!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!jOLN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png" width="1222" height="798" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:798,&quot;width&quot;:1222,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:144247,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://princygandhi.substack.com/i/209431603?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!jOLN!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 424w, https://substackcdn.com/image/fetch/$s_!jOLN!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 848w, https://substackcdn.com/image/fetch/$s_!jOLN!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 1272w, https://substackcdn.com/image/fetch/$s_!jOLN!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2f3b5db6-7532-42c9-92f5-eb2f0adc9c00_1222x798.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><span>Sorting Stack Overflow by activity rather than creation date is deliberate: a two-year-old question receiving new answers this week is a live signal, not a stale one.</span></p><p><span>All results carry dates and are deduplicated before reaching the model. Without deduplication, the same launch announcement arrives from five sources and repetition is misread as importance.</span></p><p><strong><span>Digest composition</span></strong></p><p><span>The digest (tools/email_digest.py) renders a responsive HTML email:</span></p><ul><li><p><span>A top signal block containing the single most important finding</span></p></li><li><p><span>Fixed cards for </span><strong><span>New Launches</span></strong><span> and </span><strong><span>Industry Pulse</span></strong></p></li><li><p><span>Dynamic cards generated from the subscriber&#8217;s chosen focus areas</span></p></li><li><p><span>An optional head-to-head comparison table when competitors are specified</span></p></li></ul><p><span>Requiring a single top signal acts as a forcing function. Asking a model to summarize produces several paragraphs of equal weight; asking for the most important finding produces a judgment.</span></p><p><strong><span>Prerequisites</span></strong></p><p><span>Before you begin, make sure you have:</span></p><ul><li><p><span>An AWS account with permissions for Amazon Bedrock, AWS Lambda, Amazon DynamoDB, Amazon EventBridge, and Amazon SES</span></p></li><li><p><span>Model access enabled for Anthropic Claude Sonnet 4 in Amazon Bedrock in your Region</span></p></li><li><p><span>A sender email address verified in Amazon SES</span></p></li><li><p><span>Python 3.9 or later. The codebase uses builtin generic type annotations such as list[dict], and the repository does not pin a version.</span></p></li><li><p><span>The AgentCore starter toolkit, installed with the project dependencies (bedrock-agentcore and bedrock-agentcore-starter-toolkit). The agent itself is built on strands-agents.</span></p></li><li><p><span>Optionally, a </span><a href="https://serpapi.com/"><span>SerpAPI</span></a><span> key for Google-quality search results. The free tier provides 100 searches per month; without a key the agent falls back to DuckDuckGo.</span></p></li></ul><p><span>If your SES account is in the sandbox, both sender and recipient addresses must be verified.</span></p><p><strong><span>Walkthrough</span></strong></p><p><strong><span>Step 1: Clone the repository and install dependencies</span></strong></p><p><code>git clone https://github.com/princy2310/market-intel-agent.git</code></p><p><code>cd market-intel-agent</code></p><p><code>python -m venv .venv &amp;&amp; source .venv/bin/activate</code></p><p><code>pip install -r requirements.txt</code></p><p><strong><span>Step 2: Configure the deployment</span></strong></p><p><span>Update the following values before deploying:</span></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!HL1a!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!HL1a!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 424w, https://substackcdn.com/image/fetch/$s_!HL1a!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 848w, https://substackcdn.com/image/fetch/$s_!HL1a!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 1272w, https://substackcdn.com/image/fetch/$s_!HL1a!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!HL1a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png" width="1222" height="854" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:854,&quot;width&quot;:1222,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:196060,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://princygandhi.substack.com/i/209431603?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!HL1a!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 424w, https://substackcdn.com/image/fetch/$s_!HL1a!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 848w, https://substackcdn.com/image/fetch/$s_!HL1a!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 1272w, https://substackcdn.com/image/fetch/$s_!HL1a!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7a884110-14e8-49c2-af39-6c3e103a7472_1222x854.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p><span>The agent runtime ARN does not exist until after the first deployment, which creates a chicken-and-egg ordering. Deploy the agent first, capture the ARN, then configure the Lambda functions. Only lambda_api hardcodes the ARN; the worker and trigger functions read it from the environment, so those two can be configured without editing code.</span></p><p><span>Set the optional search key as an environment variable:</span></p><p><code>export SERPAPI_KEY=your_key_here</code></p><p><code>export AWS_REGION=us-east-1</code></p><p><strong><span>Step 3: Deploy the agent</span></strong></p><p><code>agentcore configure --entrypoint agent.py --name market_intel_agent --non-interactive --create</code></p><p><code>agentcore launch</code></p><p><span>Note the runtime ARN from the output and use it to configure the three Lambda functions as described in the previous step.</span></p><p><strong><span>Step 4: Provision the supporting infrastructure</span></strong></p><p><span>The setup script creates two resources and is idempotent, so it is safe to re-run:</span></p><p><code>./setup_infra.sh</code></p><p><span>Specifically, it creates:</span></p><ul><li><p><span>The MarketIntelSubscriptions DynamoDB table, keyed on subscription_id with on-demand (PAY_PER_REQUEST) billing</span></p></li><li><p><span>The market-intel-daily EventBridge rule with the schedule expression cron(0 8 * * ? *)</span></p></li></ul><p><span>The script does </span><strong><span>not</span></strong><span> deploy the Lambda functions, create the API Gateway endpoint, or attach the EventBridge rule to a target. Deploy the three functions from the lambda_api, lambda_worker, and lambda_trigger directories, expose lambda_api through API Gateway, and add lambda_trigger as the target of the market-intel-daily rule.</span></p><p><strong><span>Step 5: Test the agent</span></strong></p><p><span>Invoke the agent directly to confirm end-to-end delivery before wiring up the UI:</span></p><p><code>agentcore invoke &#8216;{&#8221;product&#8221;: &#8220;Your Product&#8221;, &#8220;dimensions&#8221;: [&#8221;adoption&#8221;, &#8220;security&#8221;], &#8220;email&#8221;: &#8220;you@company.com&#8221;, &#8220;mode&#8221;: &#8220;full&#8221;}&#8217;</code></p><p><span>The agent runs for one to two minutes. A digest should arrive at the specified address. If nothing arrives, check that the recipient is verified in SES and review the AgentCore logs in Amazon CloudWatch.</span></p><p><strong><span>Step 6: Enable scheduled delivery</span></strong></p><p><span>The EventBridge rule invokes lambda_trigger daily at 08:00 UTC. To change the schedule, update the rule&#8217;s cron expression. Each subscription in DynamoDB produces one digest per run in daily mode.</span></p><p><strong><span>Cost considerations</span></strong></p><p><span>The solution scales to zero, so cost tracks usage rather than uptime. At approximately 50 active subscriptions, expect roughly $15 to $25 per month, with Amazon Bedrock model invocation accounting for most of it. Lambda, DynamoDB, EventBridge, and SES contribute marginally at this volume.</span></p><p><span>To control spend, reduce the number of pages fetched in step 3, run daily mode rather than full analysis on a schedule, or lower the frequency of the EventBridge rule.</span></p><p><strong><span>Cleanup</span></strong></p><p><span>To avoid ongoing charges, remove the resources you created:</span></p><p><code># Delete the DynamoDB table</code></p><p><code>aws dynamodb delete-table --table-name MarketIntelSubscriptions --region us-east-1</code></p><p><code># Remove the EventBridge target, then the rule</code></p><p><code>aws events remove-targets --rule market-intel-daily --ids 1 --region us-east-1</code></p><p><code>aws events delete-rule --name market-intel-daily --region us-east-1</code></p><p><code># Delete the worker Lambda function (the name lambda_api invokes)</code></p><p><code>aws lambda delete-function --function-name MarketIntelWorker --region us-east-1</code></p><p><span>Also delete the API Lambda function and trigger Lambda function under whatever names you deployed them with, remove the API Gateway API, and remove any SES verified identities you no longer need. Delete the AgentCore runtime through the AgentCore CLI or the Amazon Bedrock console.</span></p><p><span>Because the repository does not script Lambda or API Gateway deployment, the names of those resources depend on how you deployed them. MarketIntelWorker is the exception: lambda_api invokes it by that exact name, so it must match.</span></p><p><strong><span>Limitations</span></strong></p><p><span>Be aware of the following when evaluating results:</span></p><ul><li><p><strong><span>Search quality bounds output quality.</span></strong><span> Without a SerpAPI key, the agent falls back to DuckDuckGo, which offers weaker recency filtering.</span></p></li><li><p><strong><span>Recency is best effort.</span></strong><span> Sources expose inconsistent date metadata, so a 24-hour window is approximate rather than guaranteed.</span></p></li><li><p><strong><span>Synthesis compresses.</span></strong><span> A model reducing dozens of results to a handful of cards will omit detail. The digest links sources so findings can be verified.</span></p></li><li><p><strong><span>Coverage is public signal only,</span></strong><span> weighted toward developer and early-adopter communities. That is a real population but not necessarily a representative one.</span></p></li><li><p><strong><span>Deployment is partially manual.</span></strong><span> The setup script covers the table and schedule; Lambda, API Gateway, and the EventBridge target are left to you. Packaging the whole stack as infrastructure as code would make this reproducible.</span></p></li></ul><p><strong><span>Conclusion</span></strong></p><p><span>In this post, I deployed an autonomous research agent that replaces a recurring manual workflow with a scheduled, reproducible one. The design separates gathering from synthesis across four explicit pipeline stages, normalizes recency across 11 sources with differing date semantics, and uses asynchronous invocation so that a two-minute job does not block an HTTP request.</span></p><p><span>The pattern generalizes beyond competitive research. Any workflow that involves collecting scattered signal on a cadence and summarizing it for a human reader maps onto the same architecture: scheduled trigger, multi-source retrieval, model synthesis, and a delivery channel the recipient already checks.</span></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.com/@princygandhi/note/p-209431603&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.com/@princygandhi/note/p-209431603"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/p/how-to-automate-research-using-market?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://princygandhi.substack.com/p/how-to-automate-research-using-market?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://princygandhi.substack.com/subscribe?"><span>Subscribe now</span></a></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Build your own AI-powered IT self-service portal]]></title><description><![CDATA[Software access provisioning is a persistent source of internal friction.]]></description><link>https://princygandhi.substack.com/p/build-your-own-ai-powered-it-self</link><guid isPermaLink="false">https://princygandhi.substack.com/p/build-your-own-ai-powered-it-self</guid><dc:creator><![CDATA[Princy Gandhi]]></dc:creator><pubDate>Sat, 01 Aug 2026 23:02:27 GMT</pubDate><enclosure url="https://substackcdn.com/image/youtube/w_728,c_limit/XM2j-hruy2k" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><span>Software access provisioning is a persistent source of internal friction. An employee needs a tool, so they file a ticket or message IT. Somebody looks up whether that person&#8217;s role is permitted to have it, decides, and clicks a button. Repeat for every tool and every new hire. Most of these requests are routine and obviously approvable, yet they still wait in a queue behind a human.</span></p><p><span>In this post, I show you how to deploy AccessDesk, an IT self-service portal that automates routine access decisions and escalates only the requests that genuinely need review. The solution combines a deterministic rules engine for authorization decisions with a conversational assistant powered by Anthropic&#8217;s Claude on Amazon Bedrock for tool discovery and employee lookups. It runs on Amazon ECS with AWS Fargate, stores state in Amazon DynamoDB and sends tokenized approval emails through Amazon SES.</span></p><p><span>A key design decision, covered in detail below, is that </span><strong><span>the language model never makes the access decision</span></strong><span>. Understanding why is the most transferable part of this architecture.</span></p><div id="youtube2-XM2j-hruy2k" class="youtube-wrap" data-attrs="{&quot;videoId&quot;:&quot;XM2j-hruy2k&quot;,&quot;startTime&quot;:null,&quot;endTime&quot;:null}" data-component-name="Youtube2ToDOM"><div class="youtube-inner"><iframe src="https://www.youtube-nocookie.com/embed/XM2j-hruy2k?rel=0&amp;autoplay=0&amp;showinfo=0&amp;enablejsapi=0" frameborder="0" loading="lazy" gesture="media" allow="autoplay; fullscreen" allowautoplay="true" allowfullscreen="true" width="728" height="409"></iframe></div></div><p><span>The full source is available in the </span><a href="https://github.com/princy2310/access-desk"><span>access-desk GitHub repository</span></a><span>.</span></p><p><strong><span>Solution overview</span></strong></p><p><span>AccessDesk exposes two portals, an employee self-service catalog and a manager approval dashboard, both served by a single containerized Express application.</span></p><p><span>The following describes the request flow:</span></p><ol><li><p><span>An employee browses the software catalog in the employee portal and requests a tool.</span></p></li><li><p><span>The application invokes the rules engine, which evaluates the request against tool policy and the employee&#8217;s job level. This step is deterministic code with no model involvement.</span></p></li><li><p><span>If the employee&#8217;s job level appears in the tool&#8217;s </span><code>autoGrantJobLevels</code><span> list, the access agent grants the request immediately and records the decision. No human is involved.</span></p></li></ol><p><span>If the employee is not eligible, or the tool carries a </span><code>requiresApprovalAlways</code><strong><span> </span></strong><span>flag, the agent generates a cryptographically random approval token, records the escalation reason, and identifies the designated approver.</span></p><ol><li><p><span>Amazon SES sends the approver an email containing tokenized approve and reject links.</span></p></li><li><p><span>The approver acts, either from the email link or from the manager portal queue. The application applies the decision to Amazon DynamoDB using a conditional update.</span></p></li><li><p><span>An AWS Lambda function, invoked every 15 minutes by Amazon </span><code>EventBridge</code><span>, re-sends the approval email for requests still pending beyond the configured follow-up window, up to three times.</span></p></li></ol><p><span>Separately, both portals include a chat assistant backed by Claude on Amazon Bedrock. The assistant answers questions about the catalog, eligibility, and pending requests, and performs employee lookups for managers. It has no authority to grant access.</span></p><p><strong><span>Architecture components</span></strong></p><p><span>The CDK stack provisions:</span></p><ul><li><p><strong><span>Amazon DynamoDB</span></strong><span>: a single </span><code>TicketsTable</code><span> with composite PK/SK string keys, plus a global secondary index keyed on status and </span><code>createdAt</code><span> so the manager queue can query pending requests efficiently without a table scan</span></p></li><li><p><strong><span>Amazon ECS on AWS Fargate</span></strong><span>: a task definition with 512 MiB memory and 256 CPU units, exposing container port 3000, running behind an Application Load Balancer with a desired count of 1</span></p></li><li><p><strong><span>AWS Systems Manager Parameter Store</span></strong><span>: configuration held outside the container image: the access catalog, default approver, follow-up window, provider type, SES sender address, and portal base URL</span></p></li><li><p><strong><span>AWS Lambda and Amazon EventBridge</span></strong><span>: a reminder function on a 15-minute schedule that re-notifies approvers about stale pending requests</span></p></li><li><p><strong><span>AWS IAM</span></strong><span>: </span><code>ssm:GetParameter*</code><span> scoped to the </span><code>access-desk/*</code><span> parameter path, plus </span><code>bedrock:InvokeModel</code><span> and SES send permissions. DynamoDB access is granted through </span><code>table.grantReadWriteData</code><span>, so it is scoped to the table.</span></p></li></ul><p><span>Holding configuration in Parameter Store rather than environment variables means the access catalog and approver assignments can change without rebuilding or redeploying the container.</span></p><p><strong><span>Why the model does not decide</span></strong></p><p><span>It is tempting to hand the whole problem to a language model: accept a natural language request, return a decision. AccessDesk deliberately does not do this, for three reasons.</span></p><p><strong><span>Determinism.</span></strong><span> The same request must produce the same outcome every time. An authorization decision that varies with sampling is not a policy. Model output shifts measurably with phrasing and configuration, which is acceptable for a recommendation and unacceptable for an access grant.</span></p><p><strong><span>Auditability.</span></strong><span> When someone asks why a request was denied, the answer must be a rule rather than a rationalization. The rules engine emits a human-readable reason with every decision, for example Job level &#8216;intern&#8217; not eligible for auto-grant of Camtasia. That string is generated by policy evaluation, so it is precisely and permanently accurate about why the decision occurred.</span></p><p><strong><span>Prompt injection.</span></strong><span> As soon as model output authorizes an action, user-supplied request text becomes an attack surface. A rules engine reading a job level from DynamoDB cannot be argued with. A model reading a free-text justification field can.</span></p><p><span>The resulting principle: </span><strong><span>use the model for ambiguity, keep it away from authority.</span></strong><span> Tool discovery, phrasing, and explanation are ambiguous problems where a model excels. Authorization is not, and should not be solved probabilistically.</span></p><p><strong><span>How the components work</span></strong></p><p><strong><span>Rules engine</span></strong></p><p><span>rules-engine.js performs ordered policy evaluation:</span></p><ol><li><p><span>Check the tool&#8217;s </span><code>requiresApprovalAlways</code><span> flag. Tools such as Adobe Creative Cloud always require human review regardless of requester.</span></p></li><li><p><span>Compare the employee&#8217;s job level against the tool&#8217;s </span><code>autoGrantJobLevels</code><span> list.</span></p></li><li><p><span>Return a structured decision containing the outcome, reason, approver ID, and approver name.</span></p></li></ol><p><strong><span>Access agent</span></strong></p><p><span>access-agent.js acts on the decision. It either provisions access immediately, or generates an approval token, persists the escalation with its reason, and triggers notification to the approver.</span></p><p><strong><span>Conversational assistant</span></strong></p><p><span>The /api/chat endpoint and llm.js implement the assistant using Claude 3 Haiku on Amazon Bedrock, configurable through the BEDROCK_MODEL_ID environment variable. The endpoint supplies the model with the software catalog, the employee profile, pending requests, and lookup results as context.</span></p><p><span>For employees, the assistant handles tool discovery. A request such as &#8220;I need to record my screen&#8221; surfaces Camtasia along with eligibility context. It also answers availability and approval-status questions. For managers, it performs employee lookups by name, returning job level, department, email, and software cost, and will reason about policy when asked whether a specific request should be approved.</span></p><p><span>Haiku is chosen for latency and cost. Catalog questions do not require a frontier model.</span></p><p><strong><span>Security considerations</span></strong></p><p><span>Approval-by-email is convenient and has real sharp edges. The implementation addresses them as follows:</span></p><ul><li><p><strong><span>Token strength and lifetime.</span></strong><span> Approval tokens are 32-byte cryptographically random hex strings with a 72-hour expiry, long enough to resist guessing and short-lived enough that a forwarded email stops working.</span></p></li><li><p><strong><span>Concurrency.</span></strong><span> Two approvers acting on the same request simultaneously must not both succeed. For transitions out of </span><code>pending_approval</code><span>, the data layer applies a DynamoDB </span><code>ConditionExpression</code><span> asserting the status, so the write fails if another approver already acted. Exactly one action is applied.</span></p></li><li><p><strong><span>State machine validation.</span></strong><span> Status transitions are validated, so a request cannot skip stages.</span></p></li></ul><p><span>Two limitations to plan for before production use. First, possession of the token is sufficient to act, so a forwarded email is a transferable capability until expiry; a hardened deployment should require the approver to be authenticated rather than merely to hold a link. Second, job levels come from the application&#8217;s data layer rather than from an identity provider. A production deployment should read identity and role from your existing directory, such as through SAML or SCIM integration, rather than maintaining a parallel copy.</span></p><p><strong><span>Current gaps</span></strong></p><p><span>One thing is deliberately left incomplete in the reference implementation.</span></p><p><strong><span>Auto-grant records a decision rather than provisioning a license.</span></strong><span> The agent writes the approval and updates the employee&#8217;s granted-access list but connecting that to real entitlement assignment is per-tool integration work against each vendor&#8217;s API. That is the substantial remaining work for a production deployment, alongside reading identity from a real directory rather than the local data layer.</span></p><p><strong><span>Prerequisites</span></strong></p><p><span>Before you begin, make sure you have:</span></p><ul><li><p><span>An AWS account with permissions for Amazon Bedrock, Amazon ECS, Amazon DynamoDB, Amazon SES, AWS Lambda, Amazon EventBridge, and AWS Systems Manager</span></p></li><li><p><span>Model access enabled for Anthropic Claude 3 Haiku in Amazon Bedrock in your Region</span></p></li><li><p><span>A sender email address verified in Amazon SES</span></p></li><li><p><span>Node.js 20 or later and the AWS CDK installed. The container image builds from node:20-alpine.</span></p></li><li><p><span>Docker installed and running. The stack uses </span><code>ecs.ContainerImage.fromAsset</code><span>, so CDK builds the image locally at deploy time.</span></p></li></ul><p><strong><span>Walkthrough</span></strong></p><p><strong><span>Step 1: Run locally</span></strong></p><p><span>Local development uses an in-memory store with JSON file persistence, so state survives restarts and you can evaluate the full workflow without provisioning AWS resources. Email events are logged to the console instead of being sent.</span></p><p><code>git clone https://github.com/princy2310/access-desk.git</code></p><p><code>cd access-desk</code></p><p><code>npm install</code></p><p><code>npm run dev</code></p><p><span>Open the employee portal and submit a request for a tool your test user is not eligible for, to observe the escalation path. The chat assistant requires AWS credentials with Bedrock access even in local mode:</span></p><p><code>export AWS_REGION=us-east-1</code></p><p><code>export BEDROCK_MODEL_ID=anthropic.claude-3-haiku-20240307-v1:0</code></p><p><strong><span>Step 2: Run the test suite</span></strong></p><p><span>The project uses Vitest, including property-based tests through fast-check. Property tests are particularly valuable for a rules engine, because defects tend to appear in combinations of job level and tool configuration that individual test cases miss.</span></p><p><code>npm test</code></p><p><strong><span>Step 3: Deploy the infrastructure</span></strong></p><p><code>cd infra</code></p><p><code>npm install</code></p><p><code>npx cdk bootstrap # first time in this account and Region only</code></p><p><code>npx cdk deploy</code></p><p><span>The stack outputs </span><code>TableName, TableArn, SsmParameterPrefix</code><span>, and </span><code>AlbDnsName</code><span>. Use AlbDnsName to reach the deployed portals.</span></p><p><strong><span>Step 4: Populate configuration</span></strong></p><p><span>Set the Parameter Store values under the prefix from the stack output, including your verified SES sender address, the default approver, the follow-up reminder window, and the portal base URL. The portal base URL is used to construct approval links in outbound email, so it must match the address approvers can actually reach.</span></p><p><strong><span>Step 5: Validate end to end</span></strong></p><p><span>Submit a request that should auto-grant and confirm it provisions without human involvement. Then submit one that should escalate, confirm the approver receives an email, and approve it from the tokenized link. Check that a second click on the same link is rejected, which confirms the conditional update is working.</span></p><p><strong><span>Cleanup</span></strong></p><p><span>To avoid ongoing charges, destroy the stack:</span></p><p><code>cd infra</code></p><p><code>npx cdk destroy</code></p><p><span>This stack is not serverless and bills while idle, so cleanup matters more than it would for a Lambda-only deployment. The VPC is created with </span><code>natGateways</code><span>: 1, and a NAT Gateway carries an hourly charge plus data processing regardless of traffic. The Application Load Balancer and the Fargate task running at </span><code>desiredCount</code><span>: 1 also bill continuously.</span></p><p><span>Also remove any SES verified identities and Parameter Store entries you no longer need.</span></p><p><strong><span>Conclusion</span></strong></p><p><span>In this post, we deployed an IT self-service portal that automates routine access provisioning while preserving human review where it matters. The architecture separates a deterministic rules engine, which owns every authorization decision, from a conversational assistant that handles discovery and explanation.</span></p><p><span>That separation is the reusable idea. When you add a language model to a workflow, the instinct is to point it at the hardest-looking step. Where a wrong answer carries consequences, the better pattern is to keep the decision boring, deterministic, and auditable, and to spend the model on the surrounding work: helping people find what they need and explaining what happened.</span></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.com/@princygandhi/note/p-209429805&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.com/@princygandhi/note/p-209429805"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/p/build-your-own-ai-powered-it-self?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://princygandhi.substack.com/p/build-your-own-ai-powered-it-self?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://princygandhi.substack.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[Top five factors that change AI visibility results between chat applications and APIs]]></title><description><![CDATA[Companies use AI visibility tools to measure how often assistants mention their brands.]]></description><link>https://princygandhi.substack.com/p/top-five-factors-that-change-ai-visibility</link><guid isPermaLink="false">https://princygandhi.substack.com/p/top-five-factors-that-change-ai-visibility</guid><dc:creator><![CDATA[Princy Gandhi]]></dc:creator><pubDate>Fri, 31 Jul 2026 15:59:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kEkQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Companies use AI visibility tools to measure how often assistants mention their brands. These tools commonly send prompts to a model API and analyze the responses. Customers, however, often use the same provider&#8217;s models through a chat application or an agent.</p><p>The two interfaces can produce different results. A<span> </span><strong>harness</strong><span> </span>is the application layer around a model that supplies instructions, tools, retrieval, memory, routing, and execution logic. For example, when someone asks ChatGPT for a product recommendation, the ChatGPT harness can add account memory, decide whether to search the web, route the request to a model, and format the response with citations. An agentic development environment such as Kiro uses a harness to add workspace context, invoke tools, and manage multi-step tasks around the model.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>The following diagram shows how a request can move through an application harness.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kEkQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kEkQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 424w, https://substackcdn.com/image/fetch/$s_!kEkQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 848w, https://substackcdn.com/image/fetch/$s_!kEkQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 1272w, https://substackcdn.com/image/fetch/$s_!kEkQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kEkQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png" width="1456" height="932" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:932,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Article content&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Article content" title="Article content" srcset="https://substackcdn.com/image/fetch/$s_!kEkQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 424w, https://substackcdn.com/image/fetch/$s_!kEkQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 848w, https://substackcdn.com/image/fetch/$s_!kEkQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 1272w, https://substackcdn.com/image/fetch/$s_!kEkQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F64b7e167-c645-40b8-b302-81d6e7d8b0c1_1488x952.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 1 - A request can pass through instructions, memory, model routing, tools, and application processing before the user receives the final response.</figcaption></figure></div><p>By contrast, a direct API request might use a fixed OpenAI model with web search enabled and no conversation history. A request in ChatGPT might use account memory, search only when needed, and route the prompt between model variants. Both requests use OpenAI models, but they do not test the same system.</p><p>This post explains five harness factors that affect brand mentions and citations. It also describes what an AI visibility audit should record so that its results are useful and repeatable.</p><h3><strong>1. Web search changes the source of the answer</strong></h3><p>A model API does not search the web unless the request uses a search-enabled model or makes a search tool available. Without retrieval, the response depends mainly on information learned during model training and the content supplied in the request.</p><p>A chat application can decide to search before it answers. When it does, the retrieved pages can affect which brands appear, how they are described, and which sources receive citations. The result measures whether current web content about a brand can be found and used, in addition to what the model already knows.</p><p>This distinction creates two valid but different tests:</p><ul><li><p>A request without search measures whether the model already associates a brand with the topic.</p></li><li><p>A request with search measures whether the system can find and use current information about the brand.</p></li></ul><p>OpenAI documents this difference directly. API developers enable web search by adding the web_search tool to a Responses API request. The model can then decide whether to use it unless the developer requires the tool. ChatGPT can automatically search when a question might benefit from web information. Both interfaces can return source citations when search is used (<strong><a href="https://platform.openai.com/docs/guides/tools-web-search">OpenAI API web search</a></strong>,<span> </span><strong><a href="https://help.openai.com/en/articles/9237897-chatgpt-search">ChatGPT Search</a></strong>).</p><p>An audit should state whether search was available, whether it was required or optional, and which search tool was used. Reporting only the model name is not enough.</p><h3><strong>2. Application instructions shape the response</strong></h3><p>Chat applications can add instructions that users do not see. These instructions can define the assistant&#8217;s role, response format, safety behavior, available tools, and preference for concise or detailed answers. OpenAI&#8217;s Model Spec, for example, describes a hierarchy of platform, developer, and user instructions. It also notes that users might not see system or developer messages (<strong><a href="https://model-spec.openai.com/2025-04-11.html">OpenAI Model Spec</a></strong>).</p><p>The instructions can affect brand visibility. Consider these two directions:</p><ul><li><p>&#8220;Recommend the best product and explain why.&#8221;</p></li><li><p>&#8220;Compare at least five products and include advantages and disadvantages.&#8221;</p></li></ul><p>The first is likely to concentrate attention on one brand. The second creates more opportunities for brands to appear. A direct API test that uses different instructions will not reproduce the same behavior.</p><p>Complete application instructions are not always available to an auditor. The audit can still identify the interface used and publish any system or developer instructions that the auditor controls.</p><h3><strong>3. Memory and location personalize the result</strong></h3><p>A direct API request is usually stateless unless the application supplies prior messages or stored user data. Consumer assistants can use conversation history, saved memory, language, and location.</p><p>These signals can change a recommendation. A user who previously discussed enterprise procurement might receive different products from a user who asked about tools for a five-person startup. A local-services query can change when the user&#8217;s location changes.</p><p>OpenAI states that ChatGPT can use memories, past chats, files, and connected applications to personalize responses. ChatGPT Search can also use general IP-based location, optional device location, and relevant memories when it rewrites a search query (<strong><a href="https://help.openai.com/en/articles/8590148-memory-faq">Memory FAQ</a></strong>,<span> </span><strong><a href="https://help.openai.com/en/articles/9237897-chatgpt-search">ChatGPT Search</a></strong>).</p><p>Research supports the broader effect. One 2026 benchmark found that relevant and irrelevant memories can change how assistants interpret user intent (<strong><a href="https://arxiv.org/abs/2601.16621">How Does Personalized Memory Shape LLM Behavior?</a></strong>). A separate study of 2,050 memory entries from 80 ChatGPT users found that many entries were created automatically from prior conversations rather than only through explicit requests to remember information (<strong><a href="https://arxiv.org/abs/2602.01450">The Algorithmic Self-Portrait</a></strong>).</p><p>A single visibility score cannot represent every personalized experience. For a stable baseline, an audit can use a new session with memory disabled and record the language and region. A separate personalized test can then show how results vary for defined customer profiles.</p><h3><strong>4. The application can route prompts to different models</strong></h3><p>An API request normally names a specific model. A consumer product can select a model at runtime.</p><p>OpenAI, for example, describes GPT-5 as a system that routes requests between faster and deeper-reasoning models based on the prompt, required tools, and user intent (<strong><a href="https://openai.com/index/gpt-5-system-card/">GPT-5 System Card</a></strong>). The documented GPT-5 system can also use a smaller model after an account reaches its usage limit (<strong><a href="https://openai.com/index/introducing-gpt-5/">Introducing GPT-5</a></strong>).</p><p>This creates a tradeoff:</p><ul><li><p>A fixed API model improves repeatability.</p></li><li><p>Application-level routing better represents the product experience.</p></li></ul><p>Neither test is universally better. The choice depends on the question. A model comparison should use fixed model versions. A customer-experience audit should test the consumer application and note that the exact routed model might not be visible.</p><p>Models and routing rules also change. Every result should include the collection date. Comparisons over time should use the same interface and configuration where possible.</p><h3><strong>5. Prompt wording and execution settings affect which brands appear</strong></h3><p>Small wording changes can produce different recommendations. &#8220;Best project management software for a small team&#8221; and &#8220;easy project planning tool for a ten-person agency&#8221; express a similar need, but they provide different signals about price, industry, and ease of use.</p><p>A 2026 arXiv preprint tested approximately 6,000 paraphrase runs and 6,000 same-prompt reruns across OpenAI and Anthropic models. In that experiment, natural paraphrases of the same buyer intent produced less consistent recommendation sets than repeated use of the same prompt (<strong><a href="https://arxiv.org/abs/2605.27440">Paraphrase Brittleness in Production Retrieval-Augmented Commercial Recommendation</a></strong>). Because this is a preprint and one experimental setup, its effect sizes should not be treated as universal. The result still illustrates why an audit should test more than one phrasing for each buyer intent.</p><p>Generation settings add more variation. Temperature, output length, and repeated sampling can change the result where the selected model and API support those controls. An agentic development environment such as Kiro can also plan a task, use tools, inspect files, and revise its response. A basic API request might make one model call. Access to a model through an agentic product is therefore not equivalent to access through the model provider&#8217;s API.</p><p>To reduce this variance, group several prompt variations under the same intent and repeat each prompt. Report the average result and the variation between runs.</p><h3><strong>What changed in a controlled test</strong></h3><p>To show these factors are not just theoretical, I ran a small experiment in one category: project management software. I tracked seven brands (Asana, Trello,<span> </span><strong><a href="http://monday.com/">monday.com</a></strong>, ClickUp, Notion, Basecamp, and Jira) across three paraphrases of a single buyer intent, with three repetitions each.</p><p>The API conditions used claude-sonnet-5 and changed one factor at a time: web search on or off, a neutral instruction versus &#8220;recommend one product&#8221; versus &#8220;compare five products&#8221;, and a startup versus enterprise buyer profile supplied as context. I then ran the same three prompts in the Claude application (Sonnet, standard settings) and the ChatGPT application (default automatic model), each in fresh sessions. This is a small, category-specific test, not a general benchmark</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!GXrj!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!GXrj!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 424w, https://substackcdn.com/image/fetch/$s_!GXrj!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 848w, https://substackcdn.com/image/fetch/$s_!GXrj!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 1272w, https://substackcdn.com/image/fetch/$s_!GXrj!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!GXrj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png" width="1456" height="650" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:650,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Article content&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Article content" title="Article content" srcset="https://substackcdn.com/image/fetch/$s_!GXrj!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 424w, https://substackcdn.com/image/fetch/$s_!GXrj!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 848w, https://substackcdn.com/image/fetch/$s_!GXrj!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 1272w, https://substackcdn.com/image/fetch/$s_!GXrj!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9b428944-b438-42f5-8769-a82827ddc8f1_1484x662.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"></figcaption></figure></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ArEP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ArEP!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ArEP!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ArEP!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ArEP!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ArEP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg" width="1456" height="582" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/de745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:582,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Article content&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Article content" title="Article content" srcset="https://substackcdn.com/image/fetch/$s_!ArEP!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 424w, https://substackcdn.com/image/fetch/$s_!ArEP!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 848w, https://substackcdn.com/image/fetch/$s_!ArEP!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!ArEP!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fde745e65-c697-4553-8ae2-3885c1a41003_1600x640.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 2 - Brand mention rate by condition. Reading across a row shows how one condition&#8217;s brand set differs from the others.</figcaption></figure></div><p>What the results showed, factor by factor:</p><ul><li><p><strong>Web search.</strong><span> </span>Turning search off removed citations entirely and reordered the brands. With search on,<span> </span><strong><a href="http://monday.com/">monday.com</a></strong><span> </span>led first mentions; with search off, Trello led. Same model, different prominence.</p></li></ul><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!SrzX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!SrzX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SrzX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SrzX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SrzX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!SrzX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg" width="1440" height="810" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:810,&quot;width&quot;:1440,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Article content&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Article content" title="Article content" srcset="https://substackcdn.com/image/fetch/$s_!SrzX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 424w, https://substackcdn.com/image/fetch/$s_!SrzX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 848w, https://substackcdn.com/image/fetch/$s_!SrzX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!SrzX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0a569a54-081e-40d1-896b-36107f913ad3_1440x810.jpeg 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg aria-hidden="true" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Figure 3 - Citations per answer across Claude API conditions. Search on returned about eleven source domains per answer; search off returned none. The instruction to compare five products roughly doubled citations relative to recommending one.</figcaption></figure></div><ul><li><p><strong>Instructions.</strong><span> </span>&#8220;Recommend one product&#8221; produced 1.8 brands per answer; &#8220;compare five products&#8221; produced 3.7. The instruction, not the model, set how many competitors appeared.</p></li><li><p><strong>Buyer profile.</strong><span> </span>The enterprise profile was the sharpest shift: in 44% of its answers, none of the seven tracked tools appeared at all, because the model named enterprise platforms outside the list. The startup profile, by contrast, surfaced Trello prominently.</p></li><li><p><strong>Prompt wording.</strong><span> </span>Recommendation sets from two paraphrases of the same intent overlapped 0.58 on average, lower than the 0.67 overlap between reruns of the identical prompt. Rewording moved the results more than rerunning did.</p></li><li><p><strong>Application versus API.</strong><span> </span>The clearest single result: through the API with search, Claude cited about 11 sources per answer; through the Claude application on standard settings, the same model family returned zero citations, because the application did not search for these prompts. The recommended brands also shifted. Linear, a tool that appeared in only 11% of the baseline Claude API answers (neutral instruction, search on), appeared in 77% of the Claude application answers. Notion moved from weak in the API to prominent in the application. Across all three interfaces, however,<span> </span><strong><a href="http://monday.com/">monday.com</a></strong><span> </span>held the top share of voice, so the leader was stable while the middle of the list reshuffled.</p></li></ul><p>Two limits on these numbers. The buyer-profile conditions are a proxy for application memory, not a real memory implementation. And the application-versus-API comparison is only clean for Claude, because it holds the provider roughly constant; the ChatGPT result changes the provider and model as well, so I treat it as a separate product observation rather than a harness-only comparison. The citation metric also counts only structured citations, so it understates the ChatGPT application, which cited sources inline (frequently<span> </span><strong><a href="http://techrepublic.com/">techrepublic.com</a></strong>) without exposing them as separate links.</p><p>The takeaway is consistent with the five factors: within one category, the interface and its settings changed which brands appeared, how many appeared, and whether any sources were cited, without changing the underlying model.</p><h3><strong>My recommended reporting standard</strong></h3><p>Based on these factors, I recommend that every AI visibility report identify the complete system that produced each response. At minimum, document:</p><ol><li><p><strong>Interface:</strong><span> </span>Consumer application, API, or agentic application.</p></li><li><p><strong>Model:</strong><span> </span>Exact model version when available.</p></li><li><p><strong>Retrieval:</strong><span> </span>Whether web search or another retrieval tool was available and whether its use was required.</p></li><li><p><strong>Session state:</strong><span> </span>New or existing session, memory setting, language, and region.</p></li><li><p><strong>Instructions:</strong><span> </span>System or developer instructions controlled by the auditor.</p></li><li><p><strong>Prompts:</strong><span> </span>Full prompt set, intent groups, and paraphrases.</p></li><li><p><strong>Execution:</strong><span> </span>Generation settings, number of runs, and collection date.</p></li><li><p><strong>Evidence:</strong><span> </span>Raw responses and citations, subject to privacy and licensing limits.</p></li></ol><p>Without this context, a visibility score is difficult to reproduce and easy to misinterpret. These details explain why two tools can report different scores for the same brand. They also make before-and-after measurements more credible.</p><h3><strong>Conclusion</strong></h3><p>API and application tests answer different questions, so they should not be treated as substitutes for each other.</p><p>For repeatable experiments, model comparisons, and controlled retrieval tests, I recommend using a fixed API configuration. To understand what customers experience, test the consumer application. A complete audit uses both methods and reports the results separately.</p><p>The model name alone does not define an AI answer. The interface, tools, state, routing, and prompt all contribute to the result. AEO teams should measure the complete system and document how the measurement was made.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://substack.com/@princygandhi/note/p-209271218&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://substack.com/@princygandhi/note/p-209271218"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/p/top-five-factors-that-change-ai-visibility?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://princygandhi.substack.com/p/top-five-factors-that-change-ai-visibility?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://princygandhi.substack.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>