perplexed 0.3.0

Two analyst-grade templates land — spec profiles and market-category profiles — plus two quiet bugs the testing pass caught

Perplexed now ships templates for profiling open specs (with five-way authority typing and three-tier adoption) and named market categories (with explicit financial-stage tiers for incumbents, challengers, and innovators). The third shipped thing was a max_tokens override we built after diagnosing why the first real spec profile stopped halfway through with no warning at all. The fourth was a rendering-discipline upgrade — strengthened mermaid rules plus new LaTeX rules — after diagnosing that the vault's concept-profile template had drifted from the bundled source and was generating diagrams with no rendering rules in scope at all.

Two new templates, two quiet bug fixes, and the rendering-discipline upgrade we needed to catch them

Why care?

If you spend any time figuring out which open specs your team should care about — MCP versus A2A versus the latest agent-to-agent protocol someone is pitching at a conference — Perplexed now produces an analyst-grade profile of any one of them in a single run. Same workflow you already use for concepts and market maps: drop a file into Sources/Standards-and-Specs/, give it the spec's name, run Apply directory template to current file, and you get back a 6-9K-word draft with the spec's full authorship and stewardship history, three tiers of named implementations (incumbents, challengers, innovators), the publicly named critics, the political fault lines, and the open frontier.

If your day involves naming the companies inside a market category — "who are the actual players in agentic workspaces, sorted by stage" — there's now a separate template for that too, in concepts/Market-Categories/. It uses the same three-tier framing the spec-profile template uses, but tuned for FINANCIAL stage rather than adoption tier: Incumbents (the legacy public-and-PE-owned crowd you can't ignore), Challengers (the well-funded scale-ups eating share), Innovators (Pre-Seed through Series B). And unlike the market-map template — which caps big-tech mentions to counteract training-data bias — the market-category template wants the big tech named in Incumbents, because for a category profile, knowing the incumbents is the point.

If you author any directory template that runs on sonar-deep-research, the third shipped thing matters more than either template. We found and fixed a silent-truncation pathology where Perplexity's default max_tokens cap (~8192 tokens, ~6K words) was ending the stream cleanly mid-template — full sources footer, no Notice, no truncation flag — making it look like a healthy completion that just happened to be missing the back half. The new max-tokens: cft override lets any template that legitimately wants 6-9K words of body bump the budget; both shipped deep-research templates now declare it at 24,000.

If you've been frustrated that Perplexity-authored Mermaid diagrams come back broken roughly 4-of-5 times — bare & characters inside labels, unquoted multi-word labels with parentheses, \n instead of <br/> for line breaks — the fourth shipped thing is for you. The visible fix was strengthening the mermaid-discipline partial with paired BAD/GOOD examples and adding a new latex-discipline partial for Obsidian's MathJax delimiters. The deeper fix was diagnostic: the vault's concept-profile.md had drifted from the bundled source and was missing the {{include: mermaid-discipline}} directive entirely — meaning every concept-profile run had been generating Mermaid with no rendering rules in scope at all. Strengthening the partial wouldn't have changed a thing until the include was wired up.

What's new?

Four shipped behaviors:

zz-cf-lib/
├── templates/
│   ├── concept-profile.md
│   ├── vocabulary-profile.md
│   ├── source-profile.md
│   ├── toolkit-profile.md
│   ├── market-map-profile.md
│   ├── standards-and-specs-profile.md       (new — open specs profiler)
│   └── market-category-profile.md           (new — financial-stage tiers)
└── ...
  • standards-and-specs-profile.md — auto-seeded into Content-Dev/Templates/ and re-seedable. Matches Sources/Standards-and-Specs/** and Standards-and-Specs/**. Runs on sonar-deep-research.

  • market-category-profile.md — auto-seeded too. Matches concepts/Market-Categories/** and Market-Categories/**. Runs on sonar-deep-research.

  • max-tokens: cft-block override — new per-template knob that overrides Perplexity's default output-token cap. Number or numeric string, non-positive ignored. Both the new templates ship with max-tokens: 24000; the existing market-map-profile.md was also bumped to 24000 (it was masked from this issue by the wall-clock cap firing first; with the recent request-timeout-ms: 0 change, it would have started hitting the same silent-truncation cliff on the next thorough run).

  • Rendering-discipline partials — strengthened and expanded. The mermaid-discipline.md partial doubled in length (250 tokens → 440) with paired BAD/GOOD examples, the &amp; escape rule, the explicit \n vs <br/> ban with example, a six-item self-check before emit, and a simplify-rather-than-break ethos. A new latex-discipline.md partial (~165 tokens) covers the three Obsidian MathJax pitfalls ($$...$$ not \[...\], $...$ not \(...\), escape literal \$ in prose). Both wired into concept-profile.md (the bundled source AND the vault copy, which had drifted) immediately after the "render a mermaid codefence here" instruction so the rules are in scope at the moment of generation.

The directory-templates.md doc gained a Per-template max-tokens: override section with the diagnostic table operators need to tell wall-clock truncation apart from max-tokens truncation — a vocabulary that didn't exist before today and that we needed in the moment to debug our first real spec profile.

How the standards-and-specs profile thinks

A spec profile is the document an innovation consultant hands to a partner who has just asked "should we care about this spec?" It is not implementer documentation, not a tutorial, not marketing for the spec. The template's system prompt is explicit about this — conformance matrices, MUST/SHOULD/MAY discipline, and wire-format details are out of scope; stewardship, adoption tiers, political fault lines, and named critics are in scope.

The template handles five authority types and tunes its treatment of every downstream section based on which one the spec is:

TypeExamplesWhat changes
De-jureW3C, IETF/RFC, ISO, IEEE, ECMA, NISTEditors lifted from spec cover; working-group archives cited
Industry consortiumKhronos, Linux Foundation, OpenJS, OASIS, CNCFMember companies named with their voting weight
Vendor-led-openMCP (Anthropic), OpenAPI's early Wordnik yearsBoth originating team AND cross-vendor contributors named
Communityllms.txt (Jeremy Howard), AGENTS.md (post-OpenAI handoff)Originator's identity is the political center
De-factoREADME convention, package.json shape, the curl interfaceDominant implementation defines the spec; stewardship shifts implicitly

The skeleton has twelve sections (Snapshot → The Question this Spec Answers → Identity & Status → Why It Matters → Position in the Ecosystem Stack → Lineage → Governance & Stewardship → Adoption by Tier → Critique & Open Disputes → Frontier & Open Questions → Media, Voices, and Coverage → Adjacent Specs and Standards). Two pieces of structural discipline are load-bearing:

One — created-by and maintained-by are first-class identity. The Snapshot lede ends with an explicit one-line callout: **Created by** {name(s)} ({year}) · **Maintained by** {name(s)} · **Type:** {authority-type}. For specs where the creator and current steward differ, the Governance & Stewardship section gets a dedicated paragraph telling the transition story — when it happened, what triggered it, what changed in governance pace afterward. AGENTS.md (OpenAI → community via Sourcegraph), OpenAPI (Wordnik → SmartBear → Linux Foundation), HTTP (Tim Berners-Lee at CERN → IETF/W3C), JSON (Crockford → IETF + Ecma, two parallel stewards) — all named explicitly in the system prompt as the kind of transition the template wants surfaced with care.

Two — the three-tier adoption framing is STRUCTURAL. The Adoption section partitions named implementations into Incumbents (the canonical/reference implementations and the dominant deployed OSS, plus the commercial implementations from market leaders — big tech belongs here, do NOT suppress), Challengers (production-grade alternatives from mid-sized companies and well-funded startups), and Innovators (early-stage / experimental / research implementations exploring the spec's edges). Each tier gets 4-8 named entries plus 2-3 deeper implementation cards for the most strategically significant. After the three tiers, Notable Holdouts surfaces the orgs that explicitly declined, forked, or are running incompatible alternatives — often as informative as the adopters about where the spec made enemies.

How the market-category profile differs

Same three-tier shape, completely different definitions. The market-category template is for a concept-folder entry (concepts/Market-Categories/), not a published memo. The reader is looking for a reference card on a named market — Humanoid Robots, Agentic Workspaces, Specialized Computing for Edge Robotics — and wants to know who plays in it.

The three tiers are sorted by financial stage, not by adoption:

  • Incumbents — large public companies, tech giants, late-stage private (post-Series E or $1B+ valuation with 10+ years of operation), PE-owned behemoths. Legacy footprint with huge market presence. The companies an enterprise buyer already has a contract with, even if not in this category yet.

  • Challengers — well-funded scale-ups (Series C through pre-IPO, or recently public via SPAC/IPO with under 7 years of operation). Rapidly growing, hype-driven, capital position to credibly threaten incumbent share.

  • Innovators — Pre-Seed through Series B funded startups. Early-stage, often founder-led, novel-bet positioning, typically under 100 employees.

The editorial stance flips relative to the market-map template. Market maps cap big tech at 1 of 5-10 to counteract training-data bias — for an analyst memo, the named operators driving the curve matter more than the incumbents profiting from it. Market-CATEGORY profiles invert this: the goal is the full financial landscape of a named market, and naming the incumbents IS the goal. Both editorial stances are right for their respective documents; the templates make the disagreement explicit so neither prompt has to apologize for it.

The template also splits Why Now and What's Happening into separate sections because they answer different questions:

  • Why Now = what FORCES aligned to make this category coherent right now? (Enabling conditions: technological unlocks, regulatory shifts, capital-formation patterns, customer-behavior shifts.)

  • What's Happening = what is the current MOMENTUM? (CAGR figures with named reports, category-creation events like a defining IPO or acquisition, where capital is concentrating by tier.)

The reader leaves Why Now understanding the conditions and What's Happening understanding the velocity. And the Industry Coverage and Market Data section is sub-grouped explicitly: Market Reports (Gartner, IDC, Forrester, ABI Research, McKinsey, Frost & Sullivan…) → Industry Articles (specialized trade press, operator-bloggers) → Financial News (Bloomberg, FT, WSJ, Pitchbook, Crunchbase News, dealroom.co).

The max-tokens story — diagnosing a silent failure

Our first real run of the new standards-and-specs template was a profile of ARM (the architecture, not the company; the model classified it correctly as VENDOR-LED-OPEN). The output looked beautifully clean: full think-output at the top, Snapshot through the start of the Adoption section, all 50 cited sources rendered correctly in the footer. No truncation Notice. No "stream went idle" warning. Just… missing six of the fourteen template sections (Challengers, Innovators, Notable Holdouts, Critique, Frontier, Media, Adjacent).

This was hostile in a way the wall-clock issue from last week never was. The Humanoid Robots truncation that motivated yesterday's request-timeout-ms: override and idle-timeout port ended mid-sentence at "R" — obviously a stream abort. The ARM run ended at a clean H2 section break with the full sources footer rendered. Every diagnostic signal pointed to "healthy completion" — except eight of fourteen sections were missing.

The cause was Perplexity's default max_tokens cap (~8,192 tokens, ~6,000 words for sonar-deep-research). The stream completed cleanly via finish_reason: "length" — the model self-rationed its output budget across the skeleton, ran out, and wrapped up with a clean tail. The plugin's existing streamPerplexityToFile cleanup pipeline saw a clean stream end, rendered the sources footer, and stamped frontmatter — exactly as designed for a healthy completion, because from the stream's perspective it WAS a healthy completion.

The fix:

TS
// In buildPayload (directoryTemplateService.ts:451):
const maxTokensRaw = cfg['max-tokens'];
const maxTokens = typeof maxTokensRaw === 'number'
    ? maxTokensRaw
    : typeof maxTokensRaw === 'string'
        ? parseInt(maxTokensRaw, 10)
        : NaN;
if (Number.isFinite(maxTokens) && maxTokens > 0) {
    payload.max_tokens = maxTokens;
}

Templates can now declare max-tokens: 24000 (or any positive value) in their cft block to lift the cap. Both the new shipped templates declare 24,000 — generous enough that a thorough 6-9K-word body lands with headroom for the three-tier adoption section, deeper implementation cards, named critics, and the frontier. The market-map template was bumped to 24,000 too, because it was previously masked from this issue by the wall-clock cap firing first; with the recent request-timeout-ms: 0 change disabling that, it would have hit this silent-truncation cliff on the next thorough run.

The new doc section in directory-templates.md includes the diagnostic table operators need to distinguish the two pathologies:

SymptomCause
Mid-sentence cutoff (literally ends with a partial word or trailing punctuation); no sources footer; user sees "stream went idle" or no Notice at allWall-clock timer (request-timeout-ms:) or idle timer (stream-idle-timeout-ms:) fired during streaming
Clean section-end cutoff (last byte is a paragraph break or full sentence); sources footer renders correctly with all citations; no NoticePerplexity max_tokens cap; stream completed via finish_reason: "length"

The wall-clock and idle timers are streaming-time controls; max-tokens is an output-budget control. They're independent. A template can hit either, neither, or both. Set all three knobs generously for thorough deep-research templates and the safety mechanisms compose without interfering.

The rendering-discipline fix — and the vault-drift bug it surfaced

The other silent failure we caught today was different in kind from the max_tokens one but identical in flavor: a problem that LOOKS like a model output issue but is actually a configuration issue one layer down. Mermaid charts in concept-profile runs were coming back broken roughly 4 out of 5 times. Common failure modes: unquoted multi-word labels with parentheses (A[Raw inputs (text, audio)]), bare & characters inside labels, \n instead of <br/> for line breaks, subgraphs with spaces in titles but no quoted display title. Each broken diagram meant another round-trip to Claude to repair before the entry could ship.

The instinctive fix was to strengthen the rendering rules in the prompt. The Claude Code shell drafted an excellent BAD/GOOD-paired set of rules — about 350 tokens, more concrete than the existing abstract-rule version, with a five-item self-check before emit and an explicit "simplify the labels rather than emit a broken diagram" ethos. Drafting models follow rules better when each rule comes with a BAD → GOOD pair than when stated abstractly; that part of the diagnosis was sound.

But the deeper diagnosis was that the rules weren't the bottleneck at all:

The vault's concept-profile.md had drifted from the bundled source and was missing the {{include: mermaid-discipline}} directive entirely. The model was generating mermaid with no rendering rules in scope at all. Strengthening the partial wouldn't have changed a thing until the include was wired up in the running prompt.

This is the kind of bug a long-running plugin generates when the seeded templates are user-editable and re-seed only fills in MISSING files: as the bundled template improves over time, the vault copy stays frozen at whatever version was originally seeded. The improvements never reach the running prompt. We saw the same shape earlier in this conversation — the request-timeout-ms and max-tokens knobs both required vault-copy updates separate from the bundled-source updates, because the vault templates had user edits that the seeder won't overwrite.

The combined fix:

  • mermaid-discipline.md partial — strengthened. Original ~250 tokens, now ~440. Kept the broader special-character list and shape rules from the existing version (which were more comprehensive than what the shell drafted — =, +, backtick, brackets, braces, angle brackets, pipe, hash, LaTeX), added the BAD/GOOD pair format throughout (which is what actually shifts model behavior), added the &amp; escape rule, the explicit \n vs <br/> ban with example, the six-item self-check before emit, and the simplify-rather-than-break guidance. Per the no lazy outs discipline: no escape hatch like "describe the diagram in prose if you're unsure" — the simplify-the-labels guidance forces a working diagram with shortened text rather than a broken one with the labels the model wanted.

  • latex-discipline.md partial — new. ~165 tokens. Covers the three Obsidian MathJax pitfalls: $$...$$ for block math (not \[...\]), $...$ for inline (not \(...\)), and escape literal \$ in prose to avoid accidental inline-math spans where two unescaped dollar amounts in the same paragraph get parsed as a math delimiter pair.

  • Wired both into concept-profile.md — in both the bundled source AND the vault copy, immediately after the "render a mermaid codefence here" instruction so the rules are in scope at the moment of generation. The placement matters: rules need to be in scope BEFORE generation, not in a post-hoc "now check your work" section, because drafting models ignore later self-check instructions more often than they violate earlier in-scope rules.

The vault-drift insight generalizes beyond this fix. Any partial or template improvement we ship needs both a bundled-source update AND a manual vault-copy update for users who've already seeded the folder. The re-seed button doesn't help — it only writes missing files, not updated ones. The long-term fix is probably a "diff and patch user files" mode for re-seed (or a per-file content-hash that flags when the user's copy diverges from the shipped version); the short-term fix is that every partial or template change ships with an explicit vault-copy step in the release notes.

One follow-up the diagnosis raises: rendering-rule partials might belong as system-level preambles rather than user-prompt partials. Preambles get auto-attached to every request and live in the model's persistent-discipline scope rather than appearing inline next to template content. The current partial-include pattern requires every template author to remember to include the rules in every template that emits diagrams — and the vault-drift problem we just diagnosed is precisely the case where that discipline fails. Promoting to a preamble would make the rules apply to every run automatically, including the cases where sonar-deep-research decides to render a diagram spontaneously without the template asking for one. Deferred — we want a few cycles of evidence that the strengthened partial actually moves the 4/5 broken rate before adding the preamble plumbing.

A different editorial discipline for each template

The three deep-research templates now in the shipped set — market-map, standards-and-specs, market-category — share a common technical substrate (sonar-deep-research, idle-only timeout, max-tokens 24000, return-images false) and a common structural posture (named entities everywhere, inline citations, anti-padding discipline, deep-research analyst voice). But their editorial discipline diverges in ways that matter:

  • Market-map caps big tech at 1-of-5-10 in any sub-bucket to counteract training-data bias. The analyst's job is to name the named operators driving the curve.

  • Standards-and-specs names big tech freely in the Incumbents tier (large dominant implementations) and aggressively in the Innovators tier (where the next extensions come from). Suppression would distort the implementation landscape.

  • Market-category names big tech as the FIRST move in Incumbents because for a category profile, knowing the incumbents IS the work product. Suppression would be lying about what an enterprise buyer sees.

Each template's system prompt states its editorial stance explicitly so a future template-modifier doesn't accidentally copy the wrong one across templates. This is the kind of thing a single comment in a single template can prevent days of "why did the market-map start naming Microsoft six times" debugging.

Files touched

  • src/docs/templates/standards-and-specs-profile.md — new. Twelve-section skeleton, five-way authority typing, three-tier structural adoption framing, named editors / stewardship-transition discipline, named-critics framing. max-tokens: 24000, request-timeout-ms: 0.

  • src/docs/templates/market-category-profile.md — new. Ten-section skeleton with explicit financial-stage tier definitions, separate Why Now / What's Happening sections, sub-grouped Industry Coverage (Market Reports / Industry Articles / Financial News). Same plumbing.

  • src/docs/templates/concept-profile.md — wired in {{include: latex-discipline}} alongside the existing {{include: mermaid-discipline}}, both immediately after the "render a mermaid codefence here" instruction so the rendering rules land in scope at the moment of generation.

  • src/docs/templates/market-map-profile.md — added max-tokens: 24000 (previously masked from this issue by the wall-clock cap; needed now that request-timeout-ms: 0 disables that cap).

  • src/docs/templates/README.md — added both new templates to the shipped-templates table; updated the cft-key list to mention the new max-tokens: key.

  • src/docs/partials/mermaid-discipline.md — rewritten. ~250 tokens → ~440 tokens. Paired BAD/GOOD examples throughout, &amp; escape rule, explicit \n vs <br/> ban, six-item self-check before emit, simplify-rather-than-break ethos. Existing broader character list and shape rules preserved.

  • src/docs/partials/latex-discipline.md — new. ~165 tokens covering Obsidian MathJax pitfalls (block vs inline delimiters, escaping literal $ in prose).

  • src/services/templateSeederService.ts — registered both new templates and the new latex partial so they auto-seed into vaults and are bundled into main.js.

  • src/services/directoryTemplateService.ts — added max_tokens?: number to the PerplexityPayload interface and max-tokens resolution + payload-setting in buildPayload (function at line 451; resolution roughly 15 lines).

  • docs/directory-templates.md — added a Per-template max-tokens: override section with the wall-clock-vs-max-tokens diagnostic table; updated the cft-key list at the top.

Bundle size: main.js went from 325 KB to 351 KB across the day (the two new template files + the rewritten/new partials account for most of the increase). Both repo and vault copies deploy to identical timestamps via the existing dev wire.

What's deferred

Three follow-ups surfaced today, all worth their own future entries:

  • The cf_finish_reason frontmatter stamp. A small (10-line) addition that would write Perplexity's finish_reason value into the target file's frontmatter on every run. Would have made the ARM diagnostic instant ("oh, cf_finish_reason: length, that's a max-tokens issue, not a wall-clock issue") instead of an investigation. Deferred only because the user wanted to get the two new templates out first.

  • A "Continue directory template on current file" command. For runs that get cut off by max_tokens (or by user cancellation), a resume command would read the existing body, identify which template sections are present versus missing via skeleton diff, and re-prompt the model with only the missing sections plus a URL-keyed citation merge to keep [N] markers coherent across the two runs. Designed but not built; the design lives in this entry's conversation thread and will be promoted to a context-v/specs/ doc if the demand materializes.

  • README documentation correctness. The shipped-templates README still says "first template wins" when multiple templates match a path. The actual behavior (per main.ts:1239) is that the plugin opens a picker modal with all matching templates and the user chooses. The README hasn't been updated; today's matching scenario (concepts/Market-Categories/ files match both concept-profile and market-category-profile) made the discrepancy visible.

  • Promote rendering-discipline partials to system preambles. The current partial-include pattern relies on every template author remembering to wire in the rules — and the vault-drift bug we caught today is precisely the case where that discipline fails. Preambles auto-attach to every request and live in persistent-discipline scope, which would catch the case where sonar-deep-research decides to render a diagram spontaneously without the template asking for one. Deferred until we have a few cycles of evidence that the strengthened partial actually moves the 4/5 broken-mermaid rate.

  • A "diff and patch user files" mode for re-seed. Today's vault-drift diagnosis surfaced a long-running plugin pathology — re-seed only writes missing files, so improvements to bundled templates and partials never reach users who've already seeded the folder. A diff-and-patch mode (with explicit per-file confirmation) would close this loop. Filed as a follow-up because the short-term workaround — manually copy each updated partial/template to the vault — is fine for a single-user dev workflow but won't scale to community-plugin distribution.

References