mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 06:49:50 +00:00
0.3.0 turns Perplexed into a serious analyst tooling layer for Venture
Capital, Private Equity, and equities-trading workflows. Three new directory
templates produce 6-9K-word cited analyst drafts in a single Perplexity
Deep Research run. Underneath them, four infrastructure fixes turn
deep-research from a brittle "might work" workflow into something you can
put real analyst time behind.
The three new templates: market-map-profile (analyst memo for Known
Category or Thesis-Driven maps), standards-and-specs-profile (open-spec
profiles with five-way authority typing and three-tier adoption framing),
market-category-profile (concept-folder reference card with three
financial-stage tiers — Incumbents / Challengers / Innovators). The four
infrastructure fixes: per-template request-timeout-ms wall-clock override
(yesterday, pressure-relief valve), structural port of per-chunk
idle-timeout discipline from the legacy modal flow into the
directory-template flow (yesterday, structural fix), per-template
max-tokens Perplexity output-budget override (today, fixes silent
clean-truncation when finish_reason=length fires mid-template), and a
strengthened mermaid-discipline partial plus new latex-discipline partial
(today, fixes the 4-of-5 broken-Mermaid rate via paired BAD/GOOD examples
and a six-item pre-emit self-check).
New templates (src/docs/templates/):
- market-map-profile.md — sonar-deep-research, request-timeout-ms 2400000
(40-min ceiling), max-tokens 24000. Anti-incumbent editorial stance.
- standards-and-specs-profile.md — sonar-deep-research, request-timeout-ms 0
(idle-only safety), max-tokens 24000. Five-way authority typing, named
editors, stewardship transitions, named critics.
- market-category-profile.md — sonar-deep-research, request-timeout-ms 0,
max-tokens 24000. Three-tier financial-stage framing, separate Why Now
and What's Happening sections, Industry Coverage sub-grouped into
Market Reports / Industry Articles / Financial News.
Rendering-discipline partials (src/docs/partials/):
- mermaid-discipline.md rewritten — paired BAD/GOOD examples throughout,
the & escape rule, explicit \n vs <br/> ban, six-item self-check
before emit, simplify-rather-than-break ethos. Original ~250 tokens
expanded to ~440.
- latex-discipline.md new — Obsidian MathJax delimiters and \$ escaping
in prose to avoid accidental inline-math spans.
- concept-profile.md wired in {{include: latex-discipline}} alongside the
existing {{include: mermaid-discipline}}, placed immediately after the
"render a mermaid codefence here" instruction so the rules are in scope
at the moment of generation.
Streaming, timeout, and output-budget infrastructure (src/services/
directoryTemplateService.ts):
- streamPerplexityToFile now races each reader.read() against a fresh
per-chunk idle timer (270s deep-research, 90s normal — matches the
legacy modal flow at perplexityService.ts:659). Closes the
"Wall-clock-timeout cuts off long deep-research streams" issue.
- request-timeout-ms cft override is now the optional absolute wall-clock
ceiling (belt-and-suspenders backstop on top of the idle timer);
explicit 0 disables.
- max-tokens cft override added to buildPayload — passes through to
Perplexity's max_tokens parameter; lifts the silent ~8K-token default
ceiling that was clean-truncating thorough drafts via finish_reason:
length.
Documentation (README.md, docs/directory-templates.md):
- README — new top-level section "For Venture Capital, Private Equity, and
Equities-Trading Workflows" with a five-row table mapping each analyst
workflow to the template that produces it. Directory Templates section
updated to seven shipped templates plus the new per-template cft-block
knobs.
- docs/directory-templates.md — new "Per-template max-tokens override"
section with the diagnostic table for distinguishing wall-clock-timeout
truncation (mid-sentence cutoff, no sources footer) from max_tokens
truncation (clean section-end, full sources footer). The diagnostic
vocabulary that did not exist before this release.
Seeding (src/services/templateSeederService.ts):
- Registered all three new templates plus the new latex-discipline partial
so they auto-seed into vaults on first plugin load and appear in
Re-seed runs.
Version bump (manifest.json, package.json, versions.json):
- 0.2.1 to 0.3.0. minAppVersion unchanged at 1.8.10.
Engineering changelog entries (changelog/):
- 2026-05-26_02.md — the structural idle-timeout port.
- 2026-05-27_01.md — the two new templates, max-tokens override, and
rendering-discipline partial work. Includes the diagnostic-led
narrative on the vault-drift bug that caused the 4-of-5 broken-Mermaid
rate.
Release narratives:
- changelog/releases/0.3.0.md — full marketing-quality release narrative
with frontmatter, four-audience cascade, three diagnostic-led stories
(streaming-timeout, max_tokens, rendering-discipline), explicit upgrade
notes including the vault-drift warning, three deferred items for 0.4.x.
- release-notes/0.3.0.md — prose-only release-page body picked up by the
release.yml workflow when the 0.3.0 tag is pushed. Tighter cut of the
same narrative, suited for the GitHub release page.
Context-v stamp:
- context-v/issues/Wall-Clock-Timeout-Cuts-Off-Long-Deep-Research-Streams.md
marked status: resolved, date_resolved 2026-05-26, with the four open
items reorganized into "what landed" (3 of 4) and "deferred follow-ups"
(cross-service audit, settings-pane exposure, deep-research detection
robustness).
Files changed:
- manifest.json, package.json, versions.json
- README.md, docs/directory-templates.md
- src/services/directoryTemplateService.ts
- src/services/templateSeederService.ts
- src/docs/templates/README.md
- src/docs/templates/concept-profile.md
- src/docs/templates/market-map-profile.md
- src/docs/templates/market-category-profile.md (new)
- src/docs/templates/standards-and-specs-profile.md (new)
- src/docs/partials/mermaid-discipline.md
- src/docs/partials/latex-discipline.md (new)
- changelog/2026-05-26_02.md (new)
- changelog/2026-05-27_01.md (new)
- changelog/releases/0.3.0.md (new)
- release-notes/0.3.0.md (new)
- context-v/issues/Wall-Clock-Timeout-Cuts-Off-Long-Deep-Research-Streams.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
310 lines
24 KiB
Markdown
310 lines
24 KiB
Markdown
# Directory Templates
|
||
|
||
**Per-folder content generation for Obsidian vaults — one template fills a category of files.**
|
||
|
||
This is the long-form reference for Perplexed's directory-template system. For the short summary that lives in your vault after first plugin load, see `<your-vault>/Content-Dev/Templates/README.md` (auto-seeded from `src/docs/templates/README.md`). For the engineering changelog that introduced the feature, see [`changelog/2026-05-10_01.md`](../changelog/2026-05-10_01.md).
|
||
|
||
> The feature was originally specced as "prompt outlines" (see `context-v/specs/Using-Files-as-Prompt-Outlines.md`). The shipped naming — "directory templates" — reflects the eventual scope: it's not just an outline you point at a prompt, it's a template *bound to a directory* via a glob, with auto-seeding, frontmatter stamps, image embedding, and citation hygiene baked into the runtime.
|
||
|
||
---
|
||
|
||
## Why this exists
|
||
|
||
A working Obsidian vault collects categories of files that share a shape — concepts, vocabulary terms, sources, tooling profiles. The pre-template Perplexed flow had one mega-command per content type, each hand-tuned, each running on whichever file you happened to have open. Filling out 1,600 nearly-empty tool profiles in `Tooling/`, several hundred concepts in `concepts/`, the vocabulary, the source profiles, one editor-callback at a time, is untenable.
|
||
|
||
Directory templates close the gap. The paradigm has three primitives:
|
||
|
||
1. **A template** — a markdown file in your `Content-Dev/Templates/` folder, with frontmatter that says *which paths it applies to* via a glob (`concepts/**`), a fenced `cft` configuration block, and a heading skeleton that tells the model what to write.
|
||
2. **A runtime** — two commands (`Apply directory template to current file`, `Apply directory template to folder`) match the template to a target via the glob, build a streaming request, and write the response back as it arrives.
|
||
3. **A cleanup pipeline** — after the stream completes, the runtime wraps `<think>` blocks, swaps `[IMAGE N: …]` markers for real embeds (with a fallback `# Images` section when the model didn't emit markers but Perplexity returned images), strips unreplaced placeholder bullets, appends a `# Sources` footer in the format cite-wide can convert to hex citations, and stamps `cf_last_run` and `cf_last_run_model` into the target's frontmatter.
|
||
|
||
The combination — template + runtime + editorial stance baked into the system prompts — turns a one-file-at-a-time chore into a batched generative pass with auditable provenance.
|
||
|
||
---
|
||
|
||
## Anatomy of a template
|
||
|
||
A template file has three zones:
|
||
|
||
```markdown
|
||
---
|
||
title: Concept Profile
|
||
applies-to-paths:
|
||
- "concepts/**"
|
||
description: Encyclopedia-style entry on a concept, pattern, or mental model.
|
||
---
|
||
|
||
# Free-form intro (ignored)
|
||
|
||
Anything before the cft block is documentation for the human editing
|
||
the template. The runtime drops it from the request.
|
||
|
||
` ` `cft
|
||
provider: perplexity
|
||
model: sonar-pro
|
||
return-citations: true
|
||
return-images: true
|
||
system: |
|
||
You are writing an encyclopedia-style entry about {{basename}}.
|
||
|
||
Editorial stance:
|
||
- Tech giants are adopters/popularizers, not innovators, unless
|
||
documented heyday-era origination supports otherwise.
|
||
- Cap big tech at 1–2 of 5–7 entries in any Examples section.
|
||
- Prefer founder interviews, startup origin stories, and
|
||
research-lab papers as attribution.
|
||
` ` `
|
||
|
||
# Definition
|
||
- One-paragraph definition. Plain language. No marketing prose.
|
||
|
||
# Origins
|
||
- Who originated this concept and when. Cite the founder, paper, or
|
||
originating startup, not the company that later popularized it.
|
||
- [IMAGE 1: a diagram or screenshot illustrating the concept]
|
||
|
||
# Examples
|
||
- 5–7 real-world examples. At most 1–2 from big tech.
|
||
|
||
***
|
||
|
||
# User Notes
|
||
|
||
Anything below the *** divider is excluded from the request.
|
||
You can keep your own notes here without polluting the prompt.
|
||
```
|
||
|
||
(In the actual file, `cft` is a real code-fence — the spaces in the example above are a markdown-escape trick for this doc.)
|
||
|
||
### The three zones
|
||
|
||
1. **Frontmatter** (between `---` lines at the top) — carries `title`, `applies-to-paths` (array of globs), and an optional `description`. The runtime uses `applies-to-paths` to match a template to a target file.
|
||
2. **`cft` block** (a code fence with language `cft`) — YAML carrying `provider`, `model`, optional `search-recency`, `return-citations`, `return-images`, optional `stream-idle-timeout-ms:` and `request-timeout-ms:` (per-template timeout overrides — see *Per-template timeout overrides* below), optional `max-tokens:` (per-template output-token budget — see *Per-template max-tokens override* below), and a multi-line `system:` prompt. Everything **above** the `cft` block is dropped from the request.
|
||
3. **Heading skeleton** — the markdown structure between the `cft` block's closing fence and the first `***` divider. This becomes the user prompt. Bullets under each heading are *instructions to the model*, not literal output. Everything **below** the first `***` is excluded from the request.
|
||
|
||
### Interpolation tokens
|
||
|
||
These tokens are substituted into the `system:` prompt before the request is sent:
|
||
|
||
| Token | Resolves to |
|
||
|---|---|
|
||
| `{{basename}}` | The target file's filename without extension. **Use this for any H1 the template wants the model to emit** — never use the frontmatter `title`, which the user may have customized away from the basename. |
|
||
| `{{title}}` | The target file's frontmatter `title` field, or the basename if missing. |
|
||
| `{{today}}` | Today's date in `YYYY-MM-DD` form. |
|
||
| `{{frontmatter}}` | The whole frontmatter (whitelisted keys only) as YAML, useful for giving the model existing context about the file. |
|
||
| `{{frontmatter.<key>}}` or `{{<key>}}` | A specific frontmatter value (string, number, or array joined with commas). |
|
||
|
||
The interpolation whitelist is configurable in plugin settings (default: `title, og_description, tags, og_image`). Only whitelisted keys are exposed to prompts.
|
||
|
||
---
|
||
|
||
## How a run works
|
||
|
||
### Path matching
|
||
|
||
When you invoke **Apply directory template to current file**, the runtime walks every template in `Content-Dev/Templates/`, expands each one's `applies-to-paths` globs, and picks the first that matches the active file's vault path. If multiple templates match (overlapping globs), the first-loaded wins — specificity is not ranked. Don't write overlapping patterns.
|
||
|
||
When you invoke **Apply directory template to folder**, you get a folder picker, then a template picker (defaults to the template whose glob covers the folder), then a confirmation modal with the file count. The runtime iterates each markdown file under the folder and applies the chosen template. **Stop directory template batch** halts an in-flight batch.
|
||
|
||
### Fill vs. append
|
||
|
||
- **Fill mode** runs when the target file's body (everything after frontmatter) is empty or whitespace-only. The streamed output replaces the empty body.
|
||
- **Append mode** runs when the target file already has content. The streamed output is appended after the existing body, before the sources footer. The intent: re-running a template never destroys prior work, it accretes.
|
||
|
||
### Streaming + the cleanup pipeline
|
||
|
||
The streaming primitive is a real `fetch()` SSE connection — Obsidian's `request()` and `requestUrl` both buffer the whole response, which defeats the point. The runtime:
|
||
|
||
1. **Streams** the response, parses each `data:` chunk, and flushes accumulated text to the file every ~500ms so you see progress. Failures surface within seconds instead of after a 60-second silent wait.
|
||
2. **Captures** Perplexity's `search_results` and `images` arrays as they arrive in the SSE metadata.
|
||
3. After the stream ends, runs the cleanup pipeline:
|
||
- `wrapThinkBlocks` — converts `<think>…</think>` to fenced ` ```think-output` blocks.
|
||
- `processContentWithImages` — swaps `[IMAGE N: …]` markers (permissive regex matches `[Image …]`, `[IMAGE …]`, and the markdown-image-shaped `` variant) for `` using the captured `images` array.
|
||
- `buildFallbackImagesSection` — if no markers replaced but `images.length > 0`, emit a `# Images` block before the sources footer so images never silently vanish.
|
||
- `stripUnreplacedImagePlaceholders` — remove any `[Image embed placeholder …]` lines the model emitted but didn't replace.
|
||
- `buildSourcesFooter` — emit `***`, `# Sources`, and `[N]: [Title](URL)` reference definitions in the canonical Lossless format that cite-wide's `REFDEF_NUM_RE` recognises. Run **Convert All Citations to Hex Format** afterward to swap the numeric markers for stable hex IDs.
|
||
|
||
### Frontmatter stamps
|
||
|
||
Every successful run stamps three keys into the target's frontmatter via Obsidian's `processFrontMatter`:
|
||
|
||
| Key | Value | When |
|
||
|---|---|---|
|
||
| `cf_last_run` | ISO timestamp | Every run |
|
||
| `cf_last_run_model` | `Provider model` label (e.g. `Perplexity sonar-pro`) | Every run |
|
||
| `google_books_url` | Harvested URL | Only for book-type sources, only when not already present |
|
||
|
||
These let you query for stale files later (`cf_last_run` older than X), audit which model produced an entry, and skip Google-Books-URL lookups on subsequent runs.
|
||
|
||
---
|
||
|
||
## Shipped templates
|
||
|
||
Five templates ship inlined into `main.js` (via esbuild's `.md` text loader) and are seeded into the user's vault on first plugin load.
|
||
|
||
| File | Targets | Model | Notes |
|
||
|---|---|---|---|
|
||
| `concept-profile.md` | `concepts/**` | `sonar-pro` | Encyclopedia-style entries on ideas, patterns, mental models. Anti-incumbent editorial stance baked in. Switched off `sonar-deep-research` because deep-research is unreliable for image return. |
|
||
| `vocabulary-profile.md` | `Vocabulary/**` | `sonar-pro` | Term definitions with disambiguation through an innovation-consulting lens. |
|
||
| `source-profile.md` | `Sources/**` | `sonar-pro` | Profiles of trusted sources — books, people, channels, publications, journals, reports, events. Type-aware: the system prompt enumerates seven canonical types and the model picks one from frontmatter signals (`youtube_channel_url` → channel, `aliases` → likely book, etc.). Each section has per-type bullet shapes. |
|
||
| `toolkit-profile.md` | `Tooling/**` | `sonar-pro` | Profiles of tools, products, platforms, frameworks. |
|
||
| `market-map-profile.md` | `lost-in-public/market-maps/**`, `market-maps/**` | `sonar-deep-research` | Analyst-grade market-map drafts. Dual flavor: Known Category (Quantum Computing, Humanoid Robots) or Thesis-Driven (e.g., Neural Network Hardware as Brains for Robotics) traversing adjacent categories. Lean v1, single-stage. Skips image return by design — deep-research's image metadata is unreliable and market-map imagery is generated separately (Ideogram → frontmatter `banner_image` / `portrait_image` / `square_image`). Multi-stage v2 (RAG pre-flight to inject canonical Lossless tools/concepts as context + Claude editorial pass to emit `[[wikilink]]`s) is deferred — see the User Notes zone of the template for the roadmap. |
|
||
|
||
`source-profile` is the trickiest because `Sources/` is genuinely heterogeneous. The solution is one template, type-conditional content. Books also trigger Google Books URL handling: frontmatter `google_books_url` is used if present, otherwise the model finds it; either way the URL is harvested into frontmatter post-generation via regex, so subsequent runs skip the search.
|
||
|
||
### The anti-incumbent editorial stance
|
||
|
||
`concept-profile`, `vocabulary-profile`, and `source-profile` all embed an "editorial stance — attribute innovation correctly" block in their system prompts. The rules:
|
||
|
||
- Tech giants (Microsoft, Google, Amazon, Apple, Meta, Oracle, Salesforce, post-1990s IBM) are adopters/popularizers, not innovators, unless documented heyday-era origination or a research-lab paper supports innovator framing.
|
||
- Origins favor founder / paper / originating-startup attribution.
|
||
- "Best Real-World Examples" caps big tech at 1–2 of 5–7 entries.
|
||
- Case studies prefer narratives of smaller innovators outpacing incumbents.
|
||
|
||
This is saved as project memory in the parent monorepo so future templates inherit the rule. It's not Perplexity-specific bias — every default LLM corpus leans incumbent because that's where the training-data signal is.
|
||
|
||
---
|
||
|
||
## First-run seeder and re-seed
|
||
|
||
`templateSeederService.ts` bundles the four templates plus the user-facing README into `main.js` at build time, then writes them to the configured templates folder on `onload`. The policy is **two-tier**:
|
||
|
||
- **README** (`Content-Dev/Templates/README.md`) — always ensured present. If you delete it, the next plugin load writes it back.
|
||
- **Templates** — only seeded when the templates folder is missing or contains no non-README markdown. A folder with even one shipped template is treated as user-managed and left alone.
|
||
|
||
The **Re-seed templates** button in plugin settings (under *Directory templates*) writes any shipped file whose filename doesn't already exist. Use it after a plugin update introduced a new template, without overwriting your edits to existing ones. To force-reset a template to its shipped default, delete the file first, then click Re-seed.
|
||
|
||
---
|
||
|
||
## Writing your own template
|
||
|
||
1. Copy any shipped template under a new filename (`<your-name>-profile.md`).
|
||
2. Change the frontmatter — `title`, `description`, and especially `applies-to-paths`.
|
||
3. Rewrite the `cft` block's `system:` to your domain. Use `{{basename}}` for any H1 you want the model to emit. Add interpolation tokens (`{{frontmatter.tags}}`, etc.) where prior context should reach the model.
|
||
4. Rewrite the heading skeleton with the structure you want. Use bullets to instruct the model. Use `[IMAGE N: <description>]` markers in places where an image would help — set `return-images: true` in the cft block to activate image embedding.
|
||
5. Save it in `Content-Dev/Templates/`. The plugin picks it up on the next palette invocation — no reload needed.
|
||
|
||
Don't write overlapping `applies-to-paths` patterns across templates; the first matched wins and there's no ranking.
|
||
|
||
---
|
||
|
||
## Commands
|
||
|
||
| Command | Behavior |
|
||
|---|---|
|
||
| **Apply directory template to current file** | Match the active file's path to a template's `applies-to-paths`; fill or append according to file state. |
|
||
| **Apply directory template to folder** | Folder picker → template picker → confirmation modal with file count → batch apply. |
|
||
| **Stop directory template batch** | Halt an in-flight batch at the current file boundary. |
|
||
|
||
All three appear in the Obsidian command palette under `Perplexed: …`.
|
||
|
||
---
|
||
|
||
## Per-template timeout overrides
|
||
|
||
The runtime applies **two complementary timers** to every Perplexity stream (see [`directoryTemplateService.ts`](../src/services/directoryTemplateService.ts) `streamPerplexityToFile`):
|
||
|
||
| Timer | What it does | Default |
|
||
|---|---|---|
|
||
| **Stream-idle timeout** (per-chunk) | Each `reader.read()` is raced against a fresh timer. As long as bytes keep arriving, the timer is cleared and re-armed — the stream only dies if it goes **quiet** for the idle duration. | `270_000` ms (4.5 min) for deep-research models (matched by `/deep-research/i` against the resolved model name); `90_000` ms (1.5 min) otherwise. Matches the legacy modal flow in `perplexityService.ts:659`. |
|
||
| **Wall-clock ceiling** (absolute) | An optional `AbortController` deadline armed once at fetch time. Belt-and-suspenders backstop. | Plugin-level `settings.requestTimeoutMs` (default **30 min**). Set to **0** in settings to disable; the idle timer alone is responsible for safety. |
|
||
|
||
The idle timer is the primary safety mechanism. It catches the two pathologies that a single wall-clock timer handles poorly:
|
||
|
||
- **Silently stalled stream** (Perplexity rate-limit, socket close, upstream stall): the idle timer surfaces the failure within `idleMs` seconds rather than making the user wait the full ceiling.
|
||
- **Slow-but-healthy stream**: as long as some byte arrives every `idleMs` seconds, the stream completes naturally — it does not get cut off mid-sentence at the ceiling regardless of whether it's still producing.
|
||
|
||
Templates can override either or both via their `cft` block:
|
||
|
||
```cft
|
||
provider: perplexity
|
||
model: sonar-deep-research
|
||
stream-idle-timeout-ms: 270000 # per-chunk idle (default already 270s for deep-research)
|
||
request-timeout-ms: 2400000 # absolute ceiling (40 min)
|
||
system: |
|
||
...
|
||
```
|
||
|
||
Override semantics:
|
||
|
||
- A numeric value or a numeric string both work.
|
||
- For `stream-idle-timeout-ms:`: non-positive / non-numeric values fall through to the model-class default (270s deep, 90s normal).
|
||
- For `request-timeout-ms:`: an explicit `0` disables the ceiling entirely (idle-only). Non-numeric or missing falls through to `settings.requestTimeoutMs`.
|
||
- Overrides apply only to that template's runs.
|
||
- The runtime tolerates mid-stream truncation gracefully: a stream killed by either timer leaves a partial draft on disk with `truncated: true` flagged in the result and a Notice telling the user to re-run.
|
||
|
||
When to override the defaults:
|
||
|
||
- `sonar-deep-research` on a multi-section analyst template (market maps, in-depth research reports, sector overviews) routinely emits 6–8K-word bodies. The 270s idle default lets the slow trickle complete naturally — no override needed for the *idle* key in most cases.
|
||
- Bump `request-timeout-ms:` (or set to `0`) when you want truly unbounded healthy runs and trust the idle timer to catch stalls.
|
||
- Lower `stream-idle-timeout-ms:` for short non-deep-research templates that should fail fast (the 90s default already does this for them).
|
||
- Templates that declare a large `include-sources:` block (per the [multi-stage exploration](../context-v/explorations/Multi-Stage-Cooperative-Claude-and-Perplexity-with-RAG.md)) when that feature ships — RAG context inflation adds first-byte latency; consider bumping `stream-idle-timeout-ms:` if first-byte routinely exceeds the default.
|
||
|
||
Market-map-profile ships with `request-timeout-ms: 2400000` (40 min ceiling) as a safety cap on absolutely-runaway runs; the per-chunk idle timer (270s, inherited) is what actually keeps the stream healthy. Concept, vocabulary, source, and toolkit profiles declare neither key and inherit `settings.requestTimeoutMs` for the ceiling and the 90s idle default — they all finish comfortably under both.
|
||
|
||
---
|
||
|
||
## Per-template `max-tokens:` override
|
||
|
||
Perplexity's default output-token cap silently truncates long analyst-grade templates by ending the stream **cleanly** with `finish_reason: "length"` mid-skeleton. The symptom is hostile: the run looks like a healthy completion (sources footer renders, no Notice, no truncation warning), but the back half of the section skeleton never appears. Default is approximately 8,192 tokens for `sonar-deep-research` (~6,000 English words).
|
||
|
||
Templates can override the default by declaring `max-tokens:` inside their `cft` block:
|
||
|
||
```cft
|
||
provider: perplexity
|
||
model: sonar-deep-research
|
||
max-tokens: 24000 # ~18K-word budget
|
||
system: |
|
||
...
|
||
```
|
||
|
||
Override semantics:
|
||
|
||
- A numeric value or a numeric string both work; non-positive / non-numeric values are silently ignored and Perplexity's default applies.
|
||
- The override is per-template. Other templates continue to use Perplexity's default.
|
||
- Perplexity may itself cap the request below your declared `max-tokens:` based on the model's context window minus the prompt length. If you request more than the model can deliver, Perplexity returns the model's actual cap; your override is the ceiling you're willing to accept.
|
||
|
||
When to bump above the default:
|
||
|
||
- **Any analyst-grade template that asks for >6K words of body.** `market-map-profile` and `standards-and-specs-profile` both ship with `max-tokens: 24000` for this reason.
|
||
- **Templates with deeply nested skeletons** (3+ heading levels) — the model rations its token budget across the skeleton, and a deep skeleton means more sections competing for the same budget.
|
||
- **Templates that demand multiple cards per section** (e.g., the three-tier adoption skeleton in `standards-and-specs-profile` with deeper implementation cards). Each card takes ~150-300 tokens; 15-30 cards × 200 tokens = 3,000-6,000 tokens just for the card sections.
|
||
|
||
**The diagnostic to distinguish max-tokens truncation from wall-clock truncation:**
|
||
|
||
| Symptom | Cause |
|
||
|---|---|
|
||
| Mid-sentence cutoff (literally ends with a partial word or trailing punctuation); no sources footer; user sees "stream went idle" or no Notice at all | Wall-clock timer (`request-timeout-ms:` ceiling) 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 Notice | Perplexity `max_tokens` cap; the stream completed cleanly via `finish_reason: "length"` |
|
||
|
||
The wall-clock and idle timers are streaming-time controls; `max-tokens` is an output-budget control. They are independent — a template can hit either, neither, or both. Set both knobs generously for thorough deep-research templates.
|
||
|
||
## Settings
|
||
|
||
`Plugin settings → Directory templates`:
|
||
|
||
- **Content-Dev folder path** — root for content-development resources (default: `Content-Dev`).
|
||
- **Templates folder path** — where templates live (default: `Content-Dev/Templates`).
|
||
- **Frontmatter interpolation whitelist** — comma-separated keys exposed to `{{frontmatter.<key>}}` (default: `title, og_description, tags, og_image`).
|
||
- **Re-seed templates** — write any shipped template whose filename doesn't exist in the templates folder.
|
||
|
||
---
|
||
|
||
## Known limits and open items
|
||
|
||
(Mirrored from the changelog's *Open Items* section for completeness — see `changelog/2026-05-10_01.md` for the canonical list.)
|
||
|
||
- **Image quality on abstract concepts.** Perplexity image search keyword-matches against alt text, so it favors marketing heroes over feature/dashboard screenshots even when the latter would be more illustrative. For concept and vocabulary entries with no canonical visual referent, image search often returns nothing useful. Tier-2 mitigations (multimodal re-rank, headless screenshot service, Ideogram-generated illustrations) are deferred.
|
||
- **`<think>` block streaming UX.** Raw `<think>…</think>` blocks land in the file during streaming and only get wrapped to a fenced block at the end. A live wrap during streaming would be cleaner.
|
||
- **Multi-`cft` per template.** Each template has one `cft` block today. A multi-block template would let a single template define per-section refresh prompts. Unblocked but not designed.
|
||
- **Defensive model capture from API response.** The stamped model name comes from the cft config. If Perplexity silently substitutes a different model (rate-limit fallback), we won't reflect that. Capturing the model name from the SSE response and stamping that instead would close the loop.
|
||
- **Citation/backlink preservation under cite-wide.** The image-placeholder strip and `[IMAGE N: …]` replacement run on the streamed string before any cite-wide processing. The ordering is consistent today but isn't pre-flight-checked.
|
||
|
||
---
|
||
|
||
## See also
|
||
|
||
- [`changelog/2026-05-10_01.md`](../changelog/2026-05-10_01.md) — the changelog entry that shipped this paradigm, with full *What Was Built / What Changed in Approach / Files Touched* breakdown.
|
||
- [`src/docs/templates/README.md`](../src/docs/templates/README.md) — the shorter doc that gets seeded into the user's vault on first plugin load.
|
||
- [`context-v/specs/Using-Files-as-Prompt-Outlines.md`](../context-v/specs/Using-Files-as-Prompt-Outlines.md) — the original "outlines" spec sketch (kept as historical context; the shipped naming and shape evolved past this).
|
||
- `cite-wide` plugin's `llmCitationParserService.ts` (`REFDEF_NUM_RE`) — the `[N]: [Title](URL)` form the sources footer emits is what cite-wide's *Convert All Citations to Hex Format* command consumes.
|