Market-map analyst template + the per-template timeout override the long deep-research it needs surfaced as load-bearing
A fifth shipped directory template aimed at analyst-grade market maps (Known Category and Thesis-Driven flavors) — the kind of document a partner pays a $150K analyst three days to produce.
A template for analyst-grade market maps, and the timeout the work it produces actually needs
Why care?
If you write market maps — the kind of document where you enumerate every credible operator in a category, segment them, name the funders, surface the open questions, and hand it to a partner — Perplexed now ships a directory template that produces a respectable analyst draft in one pass. Drop a file into lost-in-public/market-maps/ (or any top-level market-maps/), give it a title and a one-line lede, run Apply directory template to current file, and you get back a 6-8K-word draft on sonar-deep-research with named innovators, per-segment funding tables, media coverage, market dynamics, and a frontier-questions section. Then you spend your time on the curation pass — promoting [Name](url) references to [[wikilink]]s back into your vault, layering in your :::tool-showcase blocks, tightening the analyst voice — instead of on the enumeration grind. The framing is straightforward: an analyst-day at $150K-a-year rates is $600 of someone's time, the API run costs a few dollars, and the rest is your judgment doing what your judgment does best.
If you author any directory template that runs on a deep-research model, the timeout refactor is the more load-bearing change. The plugin's wall-clock cap before today was 10 minutes — perfectly reasonable for concept-profile and vocabulary-profile, fatal for anything that legitimately needs 20-30 minutes to produce its long analytical body. Today's market-map run on Humanoid Robots and their Input Industries was the first observable instance: ~7,500 words of body produced, then a hard stop mid-sentence in the Frontier and Open Questions section as the AbortController fired at the ceiling. The fix: per-template request-timeout-ms: cft-block override, plugin-level default bumped to 30 min, and an open issue filed for the structural fix (port the idle-timeout discipline the legacy modal flow already uses).
What's new?
Three shipped behaviors:
zz-cf-lib/
├── templates/
│ ├── concept-profile.md
│ ├── vocabulary-profile.md
│ ├── source-profile.md
│ ├── toolkit-profile.md
│ └── market-map-profile.md (new — runs on sonar-deep-research)
├── partials/ (existing — referenced via {{include: name}})
└── preambles/ (existing — auto-attached to every request) market-map-profile.mdis auto-seeded intoContent-Dev/Templates/on first plugin load and re-seedable from settings via the Re-seed templates button. It matcheslost-in-public/market-maps/**andmarket-maps/**so both yourlost-in-public/-organized vault and a top-levelmarket-maps/folder work.request-timeout-ms:cft-block override lets a template declare its own wall-clock budget that wins over the plugin-level setting for that template's runs.market-map-profile.mddeclares2400000(40 min); the other four don't declare it and inherit the new default.Plugin-level default bumped 600000 → 1800000 (10 min → 30 min). Settings-pane description rewritten to call out the override and the cost framing ("$10-$50 of analyst time per good output is worth waiting for").
Two context-v artifacts captured at the same time, because the market-map work raised both as the next-natural moves:
context-v/explorations/Multi-Stage-Cooperative-Claude-and-Perplexity-with-RAG.md— opens the explorations folder for this submodule. Weighs three architectures for evolving the lean v1 template into a cooperative pipeline (zoned single-file appends, multi-doc folder per market map, per-sectioncft-sectionblocks); proposes aninclude-sources:YAML schema covering vault paths/globs and Chroma queries; translates the "Perplexity and Claude do not overwrite each other until editing" framing into three implementation strictness levels. Recommends Option A (zoned appends) for v1.context-v/issues/Wall-Clock-Timeout-Cuts-Off-Long-Deep-Research-Streams.md— the Humanoid Robots truncation as motivating symptom, side-by-side diagnosis of the two timeout disciplines in this codebase (wall-clock here, per-chunk idle in the legacy modal), the partial fix shipped today, why it's only partial, and the structural-fix proposal.
How the market-map template thinks
A market map is the draft a well-paid analyst hands to a partner. It is not an encyclopedia entry, not a marketing post, not a listicle. The template's system prompt commits to this framing explicitly and asks the model to determine the flavor of the map from the title and frontmatter:
Known Category — Humanoid Robots, Quantum Computing, Agentic AI in Fintech. The taxonomy is roughly settled; the work is enumerating innovators within established sub-segments and explaining the frontier.
Thesis-Driven — Neural Network Hardware as Brains for Robotics, Blockchain and Web3 Institutional Invasions. A hypothesis traverses multiple known categories. The work is making the thesis legible, then enumerating innovators from each adjacent category the thesis pulls in.
The skeleton works for both flavors. Sections (in order):
Market Snapshot — italicized lede + headline-stat blockquote + orienting paragraph.
The Question this Map Answers — what this map clarifies for an operator or investor reading it.
Why Now — 3-5 cited unlocks (technical thresholds, regulatory shifts, capital patterns, customer-behavior shifts) that make the market mappable in the current quarter.
Map of the Market — Sub-Segments — 4-8 sub-segments with 1-2 sentence definitions. Doubles as the TOC for the next section.
Lighthouse Examples — 5-10 named operators per sub-segment in
[Name](url) — one-lineform, flat reference list.Innovator Profiles — 3-7 deeper cards per sub-segment in a strict Link / Offering / Funding / Why-they-matter / Coverage shape, followed by a per-sub-segment summary table.
Media, Voices, and Coverage — publications, podcasts, YouTubers, analysts, operator-thinkers.
Market Dynamics — Sizing & Growth, Adoption Patterns & Barriers, Capital Flow.
Frontier and Open Questions — 4-7 specific questions phrased as questions.
Adjacent Concepts and Maps — plain-text concept names (no wikilinks invented; the curator wikilinks during curation).
The anti-incumbent editorial stance from the concept-profile family carries over: big tech is treated as adopters/popularizers, not innovators, unless a research-lab paper or heyday-era origination supports otherwise. Cap big tech at 1 of 5-10 in every sub-bucket. The market-map prompt phrases this in terms specific to category mapping — "name the named operators driving the curve, not the incumbents profiting from it."
Image-return is off by design for this template. sonar-deep-research returns better citation density and analytical length than sonar-pro but its image metadata is unreliable; market-map imagery (banner, portrait, square) is generated separately via Ideogram and lives in frontmatter, so the per-section image markers the other templates use aren't load-bearing here.
How the timeout override works
The directory-template flow has always armed a single wall-clock setTimeout at fetch time, racing the entire stream against settings.requestTimeoutMs:
const controller = new AbortController();
const timer = activeWindow.setTimeout(() => controller.abort(), timeoutMs); This worked fine when the entire suite of shipped templates ran on sonar-pro and finished in 2-3 minutes. It doesn't work when a single template legitimately needs 25-30 minutes. The naïve fix would have been to bump settings.requestTimeoutMs to something generous for everyone, but that punishes the short-template case (a 90-second concept run that stalls silently shouldn't take 30 minutes to surface). So we put the dial where the cft already lives — inside the template itself:
provider: perplexity
model: sonar-deep-research
return-citations: true
return-images: false
request-timeout-ms: 2400000 # 40 minutes for this template specifically
system: |
... Resolution sits in directoryTemplateService.ts just before the streamPerplexityToFile call:
const cftTimeoutRaw = template.cftConfig['request-timeout-ms'];
const cftTimeoutMs = typeof cftTimeoutRaw === 'number'
? cftTimeoutRaw
: typeof cftTimeoutRaw === 'string'
? parseInt(cftTimeoutRaw, 10)
: NaN;
const effectiveTimeoutMs = Number.isFinite(cftTimeoutMs) && cftTimeoutMs > 0
? cftTimeoutMs
: settings.requestTimeoutMs; Number or numeric-string both work. Non-positive / non-numeric values are silently ignored and the plugin-level default takes over — the template author can't accidentally lock themselves out of the run with a typo.
Plugin-level default bumped because the old 10-minute ceiling was clearly tuned for the original four templates and not for any subsequent deep-research template anyone might write. 30 minutes is the conservative new default: comfortably above what any of the existing four templates need, generous enough that most new deep-research templates won't need to override, and visible-enough in the settings pane that a user who hits a longer-running case can find the dial.
The bug that surfaced this — Humanoid Robots, line 400
The first real-world run of market-map-profile was lost-in-public/market-maps/Humanoid Robots and their Input Industries.md. The model produced a draft that looked respectable through nine of the ten skeleton sections, then terminated mid-sentence inside the tenth:
Will the evolution of robot-as-a-service (R That trailing (R is the last byte written to the file. The Adjacent Concepts and Maps heading never appeared. Frontmatter stamps (cf_last_run, cf_last_run_model) landed correctly — the run did make it to its processFrontMatter post-step — so what was lost was streamed body content that hadn't arrived when the AbortController fired. Classic wall-clock cut-off shape.
The directory-template flow uses a single wall-clock timeout. The legacy modal flow (perplexityService.ts:659-668) has used a per-chunk idle-timeout for several iterations:
const STREAM_IDLE_TIMEOUT_MS = isDeepResearch ? 270_000 : 90_000;
const readWithIdleTimeout = (): Promise<...> => {
let timer: number | undefined;
const timeout = new Promise<never>((_, reject) => {
timer = window.setTimeout(() => {
reject(new Error(`stream went idle for ${...}s ...`));
}, STREAM_IDLE_TIMEOUT_MS);
});
return Promise.race([reader.read(), timeout]).finally(() => {
if (timer !== undefined) window.clearTimeout(timer);
});
}; The idle-timeout pattern is structurally correct. Per reader.read(), a fresh timeout is armed; as long as bytes keep arriving, the timer keeps getting cleared and re-armed. The stream is only killed if it goes quiet for N seconds. Slow-but-healthy completes; stalled-but-silent fails fast.
We didn't port the idle-timeout pattern into the directory-template flow today, because the structural refactor warrants its own design and its own commit, not a side-quest in a template-shipping changelog. Today's fix is the pressure-relief valve: a bigger ceiling, plus a per-template dial. The structural fix is filed at context-v/issues/Wall-Clock-Timeout-Cuts-Off-Long-Deep-Research-Streams.md with a 4-step concrete plan, a key-rename proposal (stream-idle-timeout-ms:), and a backstop-or-no-backstop open question.
Under the hood — the things that earned their own design decisions
Both flavors with one template, not two. The Known Category vs Thesis-Driven distinction is real, but it lives in the content of the map, not its structure. A Known Category map and a Thesis-Driven map both want sub-segments, both want innovator profiles, both want the same frontier-questions framing. The system prompt names the flavor difference once and asks the model to pick from {{title}} and {{frontmatter}} signals. Two templates would have produced more drift than value.
No invented wikilinks. The model doesn't know what's in your vault. If it tries to emit [[Tooling/AI-Toolkit/Agentic AI/Crew AI]] from training data, it'll get the path wrong half the time and the basename wrong the rest of the time. So the prompt is explicit: use [Name](url) form for every entity, leave wikilink promotion to the curator. The future multi-stage pipeline (per the exploration doc) will inject canonical vault content via an include-sources: block at request time and authorize the model to emit [[wikilink]]s for the entries it's been shown — but that's v2.
return-images: false because deep-research's image metadata isn't reliable enough. This is a documented limit on sonar-deep-research we've eaten before in the concept-profile changelog (2026-05-12). Market-map imagery doesn't lean on inline images anyway — banner_image, portrait_image, square_image in frontmatter is the canonical surface, generated separately via Ideogram.
The override key is named request-timeout-ms:, not stream-idle-timeout-ms:. That'll likely change when the structural fix lands and the underlying primitive moves from wall-clock to idle-timeout. We chose the misnomer-tolerant present-day name on purpose: the cft schema can rename keys with a compatibility alias in one release, and naming the new key for what it actually does today keeps the surface honest until the underlying mechanism changes.
Length not trimmed. The market-map template's system prompt is over 1,000 words of guidance. The skeleton with bullet instructions is another 1,000+. A first instinct was to trim — most prompts of that length are over-engineered — but every section of the template's prompt is doing real work: the editorial stance, the innovator-card shape, the dual-flavor detection, the wikilink discipline, the calibration on length ("lean long, not short"). We left it long. The cost is a longer first-time-readable system prompt for vault users; the benefit is fewer regenerations and a draft that's closer to a partner-ready document.
Files touched
plugin-modules/perplexed/
├── src/docs/templates/
│ ├── market-map-profile.md (new)
│ └── README.md (cft-block-keys list now mentions request-timeout-ms)
├── src/services/
│ ├── directoryTemplateService.ts (request-timeout-ms override resolution)
│ └── templateSeederService.ts (market-map-profile registered as 5th seeded template)
├── docs/
│ └── directory-templates.md (shipped-templates table now has market-map row;
│ new "Per-template timeout override" section)
├── context-v/
│ ├── explorations/ (new folder; opens with the multi-stage doc)
│ │ └── Multi-Stage-Cooperative-Claude-and-Perplexity-with-RAG.md
│ └── issues/
│ └── Wall-Clock-Timeout-Cuts-Off-Long-Deep-Research-Streams.md
└── main.ts
├── directoryTemplatesRequestTimeoutMs default: 600000 → 1800000
└── settings-pane description rewritten with the override + cost framing Pointer-bumped at the parent: content-farm commit e31b982 folds in the perplexed pointer (a44f23b) and a 3-line drag-along addition to context-v/plans/Create-a-Study-of-the-Best-Obsidian-Plugins.md (neural-composer by oscampo flagged as a study-candidate plugin).
What's next
Run the rebuilt plugin against Humanoid Robots in append mode to extend the truncated tail. With the 40-minute ceiling on the market-map template, the Frontier and Open Questions section should finish and Adjacent Concepts and Maps should land for the first time.
The structural idle-timeout fix. The wall-clock primitive in
directoryTemplateService.ts:511has a known better replacement living in the same codebase (perplexityService.ts:659-668). The plan and the open design questions live atcontext-v/issues/Wall-Clock-Timeout-Cuts-Off-Long-Deep-Research-Streams.md. Worth doing before the multi-stage spec lands, because stage 3 (Claude editorial on a 7-8K-word draft) will hit the same pathology as stage 2 (Perplexity research).The multi-stage Claude+Perplexity+RAG spec. The exploration doc commits to a tentative direction (Option A — zoned single-file appends with
include-sources:andeditor:keys added to the cft schema). The next move is a spec incontext-v/specs/pinning down the schema additions, the zone-delimiter format, the resolution order forinclude-sources, and the migration path for the existing four templates. Then a chunked prompt incontext-v/plans/. Thenmarket-map-profilebecomes the reference implementation.The anti-incumbent editorial stance is now duplicated across four templates.
concept-profile,vocabulary-profile,source-profile, and nowmarket-map-profileall carry a variation. Per the partials/preambles work that landed 2026-05-19, this is the next obvious extraction —partials/editorial-stance-anti-incumbent.mdreferenced via{{include: editorial-stance-anti-incumbent}}. Not done today; flagged.