Linked Interviews¶
In real-world document workflows, one interview often leads to another. A triage questionnaire might determine which of several agreement types are appropriate. A master services agreement naturally leads to a data processing agreement, an SLA, or a statement of work. VIBE's linked interviews let you connect these interviews while keeping each one fully independent.
What Can You Do With Linked Interviews?
- Triage routing: A short interview determines which of several follow-up interviews are relevant, then offers "Continue" links to each
- Satellite interviews: A primary interview (e.g. MSA) recommends related follow-up interviews (DPA, SLA, SOW) that share common data
- Data carry-forward: Shared data (party names, contract details) flows from one interview to the next without re-asking the same questions
- Full independence: Every template works on its own — links are optional connections, never hard dependencies
How It Relates to Other VIBE Features¶
If you're familiar with other VIBE features, linked interviews will feel natural because they build on the same patterns:
| Feature | What it does | Where the result goes |
|---|---|---|
insert() |
Pulls a component into the current document | Same document, inline |
appendix() |
Creates a separate document from a component | Separate file, same interview |
recommend() |
Links to a separate interview with its own template | Separate interview session, separate document |
Think of it as a progression of scope:
insert()— reuse within a documentappendix()— separate document, same interviewrecommend()— separate interview, separate document
All three functions follow the same calling convention: a positional identifier followed by optional keyword arguments for data mapping.
The _linked Namespace¶
When a user follows a recommend() link, the source interview's complete
answered state becomes available in the target template under
_linked.<source_template_id>. This is analogous to how _components
provides access to component data.
{# In beredskapsavtal, after following a link from verktygsvaljaren #}
{% if _linked.verktygsvaljaren is defined %}
Leveransomfattning: {{ _linked.verktygsvaljaren.leveransomfattning }}
{% endif %}
Key properties:
_linkedalways exists — it is seeded as an empty container on every session, so{% if _linked.X is defined %}never causes an undefined error._linked.<template_id>only exists when linked — the subtree is only present when the user followed arecommend()link from that template.- Read-only — linked values cannot be modified by the user in the target interview. This ensures coherence across the document package.
- No declaration needed — unlike
uses:for session context,_linkeddoes not require any configuration in the target template'sconfig.yml. Any template can read_linkedif data happens to be there.
Checking for Linked Data¶
Use the Jinja is defined test to check for container nodes (whether a
source template is linked at all), and the defined() function to check for
specific scalar values:
{# Check if we were linked from the MSA interview #}
{% if _linked.msa is defined %}
Client: {{ _linked.msa.party_info.client_name }}
{% endif %}
{# Check if a specific field has a value #}
{% if defined("_linked.msa.party_info.client_name") %}
Client: {{ _linked.msa.party_info.client_name }}
{% endif %}
The is defined test checks whether a key exists in the template context
(including container nodes). The defined() function checks whether a
specific path has a non-None scalar value in the session state.
What Gets Linked¶
When the user follows a recommendation link, the entire answered state of
the source interview is copied under _linked.<template_id>. Internal keys
(_components, _appendices, _linked) are filtered out — only the
template's own questions and session context values are included.
Your First Linked Interview¶
Let's build a simple example: a main contract interview that recommends a follow-up NDA interview when confidentiality is needed.
Step 1: Create the Main Interview¶
The main template collects party info and recommends the NDA when appropriate.
# contract/config.yml
title: Service Agreement
questions:
client_name:
type: text
label: Client name
client_org_nr:
type: text
label: Organization number
service_description:
type: text
label: Describe the services
confidentiality_needed:
type: bool
label: Does this engagement involve confidential information?
<!-- contract/template.md -->
# Service Agreement
This agreement is between our company and {{ client_name }}
(org. nr {{ client_org_nr }}).
## Services
{{ service_description }}
## Confidentiality
{% if confidentiality_needed %}
The parties acknowledge that confidential information may be exchanged.
A separate Non-Disclosure Agreement shall be executed.
{{ recommend('nda', "Non-Disclosure Agreement") }}
{% endif %}
Step 2: Create the Follow-Up Interview¶
The NDA template is a fully independent interview. It uses _linked to
access data from the contract interview when available, and falls back to
asking its own questions when launched standalone:
# nda/config.yml
title: Non-Disclosure Agreement
questions:
client_name:
type: text
label: Client name
confidentiality_period:
type: number
label: Confidentiality period (years)
covers_employees:
type: bool
label: Does the NDA cover employees of both parties?
<!-- nda/template.md -->
# Non-Disclosure Agreement
{% if _linked.contract is defined %}
{# Linked from the contract — use the contract's client name #}
This NDA is between our company and {{ _linked.contract.client_name }}.
{% else %}
{# Standalone — use our own question #}
This NDA is between our company and {{ client_name }}.
{% endif %}
The confidentiality obligations shall remain in effect for
{{ confidentiality_period }} years.
{% if covers_employees %}
The obligations extend to all employees and contractors of both parties.
{% endif %}
What Happens¶
NDA accessed directly (standalone mode):
- User navigates to the NDA interview
_linked.contractis not defined (no link was followed)- User fills in client name plus NDA-specific questions
- Document is generated
NDA accessed from the Service Agreement (linked mode):
- User completes the Service Agreement interview
- Completion page shows: "Non-Disclosure Agreement [Continue →]"
- User clicks "Continue"
- A new NDA interview starts with the contract's full state available
under
_linked.contract - The template's
{% if _linked.contract is defined %}branch activates, using the contract's client name - Only NDA-specific questions are asked (confidentiality period, etc.)
- Document is generated
The NDA template works in both cases. The only difference is whether
_linked.contract is available.
The recommend() Function¶
{{ recommend('template_id') }}
{{ recommend('template_id', "Display label") }}
{{ recommend('template_id', "Display label", target_name=source_value) }}
Parameters:
| Parameter | Position | Required | Description |
|---|---|---|---|
template_id |
1st positional | Yes | The identifier of the target template |
label |
2nd positional | No | Display name shown on the completion page. Falls back to the target template's title from config.yml |
**kwargs |
Keyword | No | Explicit name mappings for uses:-based session context when names differ |
This follows the same calling convention as insert(): positional arguments
identify the target, keyword arguments map data.
Behavior:
recommend()renders as empty string in the document — it produces no visible output. The recommendation text around it is normal template content that you write yourself.- Recommendations are discovered by probing, just like
insert()andappendix(). If arecommend()call is inside an{% if %}block that doesn't apply, it's never reached and the recommendation isn't offered. - Multiple
recommend()calls can be active at the same time (e.g. the MSA recommending both a DPA and an SLA). recommend()targets are validated when the template is loaded. If a target template ID does not exist, validation fails early.
How Data Flows Between Interviews¶
When a user clicks "Continue" on a recommendation link:
- The source interview's complete answered state is serialized
- Internal keys (
_components,_appendices,_linked) are filtered out - The remaining state is placed under
_linked.<source_template_id>in the target interview's session state - The target template accesses it via
{{ _linked.<source_template_id>.field }}
No configuration is needed on either side for this to work. The source
template calls recommend('target'), and the target template reads
_linked.source — that's it.
Explicit Mappings via uses: Declarations¶
When the target template has uses: declarations (with definition-only
components), recommend() keyword arguments can map source values to the
target's uses: aliases. This uses the same mechanism as session context
validation but is independent of _linked — the mapped data arrives under
the alias name, not under _linked:
{# Source calls it kund_info, target's uses: expects party_info #}
{{ recommend('dpa', party_info=kund_info) }}
No Data Forwarding¶
If recommend() is called without keyword arguments, the _linked data still
flows automatically. The target template decides what to use from it. For
triage scenarios where the triage asks routing questions but the follow-up
interviews collect their own data independently, _linked is simply ignored:
Linked Values Are Read-Only¶
Linked values are read-only in the target interview. This ensures coherence across the document package — if the MSA says the client is "Acme Corp", the DPA must agree.
This parallels how session context works: values from external systems are visible in the template but cannot be changed by the user during the interview.
Relationship to Session Context and uses:¶
The uses: mechanism in config.yml serves a different purpose from
_linked. Here is how they compare:
| Mechanism | Data source | Configuration | Editable? |
|---|---|---|---|
Session context (uses:, required) |
External system (auth token) | config.yml uses: |
No |
uses: mapping via recommend() |
A completed VIBE interview | config.yml uses: |
No |
_linked |
A completed VIBE interview | None needed | No |
| User answers | The user during the interview | config.yml questions |
Yes |
uses: with definition-only components validates that structured data
has the right shape. The data can come from external systems (PASETO tokens)
or from another interview via recommend() kwargs — the mechanism is the
same, but the data arrives with different ValueSource tags internally.
_linked provides raw access to another interview's full answered state.
No declarations are needed — the target template reads what's there, guarded
by {% if _linked.X is defined %}.
Both can coexist. A template can use uses: declarations for validated
structured data AND read _linked for ad-hoc access to the source
interview's answers.
Example: Triage Interview¶
A triage interview asks a few high-level questions and then routes the user to the appropriate follow-up:
# triage/config.yml
title: Agreement Selector
questions:
myndighet_namn:
type: text
label: Myndighetens namn
reason:
type: enum
multiple: true
label: Why does your organization need this agreement?
options:
samverkan: Establish joint planning
investering: Specific investments needed
leverans: Secure delivery of goods/services
# ... more triage questions
<!-- triage/template.md -->
{{ reason }}
## Our Recommendation
{% if 'samverkan' in reason %}
Based on your answers, we recommend a non-compensatory preparedness agreement.
{{ recommend('ersättningslöst', "Ersättningslöst beredskapsavtal") }}
{% endif %}
{% if 'leverans' in reason %}
We recommend preparedness conditions for public procurement.
{{ recommend('beredskapsavtal', "Beredskapsavtal") }}
{% endif %}
Each follow-up template can read the triage answers via _linked.triage:
<!-- beredskapsavtal/beredskapsavtal.md -->
{% if _linked.triage is defined %}
Myndighet: {{ _linked.triage.myndighet_namn }}
{% endif %}
After completing the triage, the user sees only the relevant follow-up links. Multiple recommendations can be active simultaneously.
Example: Primary + Satellite Interviews¶
An IT sourcing MSA that leads to optional DPA, SOW, and SLA interviews:
# msa/config.yml
title: Master Services Agreement
questions:
client_name:
type: text
label: Client name
client_org_nr:
type: text
label: Organization number
data_processing:
type: bool
label: Does this engagement involve processing personal data?
service_description:
type: text
label: Describe the services
<!-- msa/template.md -->
# Master Services Agreement
This MSA is between {{ client_name }} and our company.
## Services
{{ service_description }}
## Related Agreements
{% if data_processing %}
Personal data will be processed under this agreement. A separate Data
Processing Agreement is required.
{{ recommend('dpa', "Data Processing Agreement") }}
{% endif %}
{{ recommend('sla', "Service Level Agreement") }}
Each follow-up template reads shared data from _linked.msa:
<!-- dpa/template.md -->
# Data Processing Agreement
{% if _linked.msa is defined %}
This DPA supplements the Master Services Agreement between
{{ _linked.msa.client_name }} (org. nr {{ _linked.msa.client_org_nr }})
and our company.
{% else %}
{# Standalone mode — ask our own questions #}
This DPA is between {{ client_name }} and our company.
{% endif %}
Each follow-up template works independently. When accessed from the MSA,
_linked.msa provides the shared data. When accessed standalone, the template
falls back to its own questions.
Passing _linked Data to Components¶
The _linked namespace is NOT automatically passed through to components
(it starts with _, following the same convention as _components). If a
component needs linked data, pass it explicitly via insert() kwargs or
component_defaults:
{# Pass specific linked values to a component #}
{{ insert('signature_block',
signer_name=_linked.msa.client_name) }}
# Or via component_defaults in config.yml
component_defaults:
client_name: "{{ _linked.msa.client_name if _linked.msa is defined else client_name }}"
How Recommendations Appear in the UI¶
During the interview¶
As you answer questions and probing discovers active recommend() calls, links
to the recommended follow-up interviews appear below the main question list.
These links are not shown until the relevant recommend() call is actually
reached by probing — if it's inside an {% if %} block that hasn't been
triggered yet, there's nothing to show.
On the completion page¶
When the interview completes, the completion page shows the document download and the recommended follow-ups:
[x] Master Services Agreement is ready. [Download DOCX]
━━━ Recommended follow-ups ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
● Data Processing Agreement [Continue →]
● Service Level Agreement [Continue →]
Only active recommendations appear — those inside {% if %} blocks that
evaluated to true. The user chooses which follow-ups to pursue. Each "Continue"
link starts a fresh interview session for the target template.
Back-link Navigation¶
When an interview is launched from a linked recommendation, it knows where it came from. This enables optional "← Back to Master Services Agreement" navigation in the UI. The back-link is purely informational — the target template has no functional dependency on the source.
When the same template is launched standalone, there is no back-link.
Key Differences from Components and Appendices¶
insert() |
appendix() |
recommend() |
|
|---|---|---|---|
| Target | A component | A component | A template |
| Output | Content in your document | Separate file, same package | Separate document from a separate interview |
| Questions | Injected into current interview | Injected into current interview | Asked in a new interview session |
| Data sharing | Via keyword arguments | Via keyword arguments | Automatic via _linked, or keyword args for uses: mappings |
| User experience | Seamless — part of one interview | Seamless — part of one interview | A new interview starts after the current one completes |
| Independence | Component can't run alone | Component can't run alone | Target template is fully independent |
| Authorship | Usually same author | Usually same author | Can be different teams |
Tips and Best Practices¶
Guard with {% if _linked.X is defined %}. Always check before accessing
linked data. The template must work standalone too.
Keep triage templates focused. A triage interview should ask only the questions needed to determine which follow-ups are relevant. Leave detailed questions to the follow-up templates.
Write the recommendation text yourself. recommend() renders nothing
visible — it just registers the link. Write the explanatory text around it as
normal template content. This gives you full control over the wording.
Pass linked data to components explicitly. Use insert() kwargs or
component_defaults to forward _linked values to components that need them.
Test templates both standalone and linked. Since every template must work
independently, test each follow-up template on its own first. Then test the
link to verify that _linked data arrives correctly.
Multiple recommendations can be active. It's perfectly valid for a triage to recommend two or three follow-ups simultaneously. The user sees all of them on the completion page and can pursue any or all.