Methodology Modes: Choosing Mode 1, 2, or 3
Source:vignettes/methodology-modes.Rmd
methodology-modes.RmdWhy three modes?
pakhom’s architectural commitment AC1
says: AI is scaffold by architecture, not by configuration.
Most “AI for thematic analysis” tools collapse the methodological
question (“what is the AI’s role here?”) into a configuration option
(“which prompt template do I want?”). pakhom inverts that: the AI’s role
is determined by which of three methodology modes you
declare, and the package code enforces the commitments that flow from
that mode: what the AI may produce, what the researcher must author,
which transparency artifacts are mandatory, and which pause-points are
required.
The mode declaration is mandatory in every config (no default); it is
locked at run start and stamped onto every output. Any change creates a
fork run with parent_run_id linkage, so
the methodology trail is always reconstructable.
This vignette walks through each mode with a worked example, then gives a decision rubric for choosing among them.
Mode 1: Reflexive Scaffold (AI as provocateur)
“AI Should Challenge, Not Obey” (Sarkar 2024, CACM Oct 2024).
In Mode 1, the AI never names themes, codes, or interpretations. The researcher authors the analytic frame (typically in NVivo, ATLAS.ti, or MAXQDA). pakhom contributes the provocateur loop: five extractive questioning categories that surface counter-evidence the researcher’s framing might overlook.
The five provocation categories
| Category | What the AI returns |
|---|---|
counter_narrative |
Up to N entries, drawn from the supporting entries plus a bounded sample of non-theme corpus entries shown in the prompt, that frame the construct as not-Y (challenges the theme’s framing) |
disconfirming_evidence |
Entries from the same prompt context that directly contradict the theme |
alternative_interpretation |
Methodologically-defensible alternative theme names that the same supporting quotes could support (without saying which is better) |
absent_voice |
Demographic / temporal / linguistic / topical segments of the corpus that are underrepresented in the theme’s supporting entries |
assumption_surfacing |
Other terms participants use for the same construct + a term the researcher’s framing erases |
Each provocation that cites a verbatim quote runs through the same
verification ladder used in Modes 2/3. A fabricated quote is dropped
silently and logged to fabrication_log.csv: fabricated
provocations never reach the researcher.
Worked example
library(pakhom)
# 1. Author your themes elsewhere (e.g., NVivo) and load them as a ThemeSet.
# pakhom never writes themes in Mode 1.
my_themes <- create_theme_set(list(
list(id = 1, name = "Adoption",
description = "Researcher-authored: async-communication adoption behaviors",
codes_included = c("async_routine", "daily_batching")),
list(id = 2, name = "Resistance",
description = "Researcher-authored: resistance to the new norms",
codes_included = c("skips_async", "tool_friction"))
))
# 2. Your corpus -- a tibble with std_id + std_text. theme_membership_*
# columns indicate which entries support each theme. (Author IDs in
# std_author drive the participant-spread metric.) In practice
# you'd build this tibble from a database load + your existing NVivo
# coding; here a small toy corpus illustrates the shape.
my_corpus <- tibble::tibble(
std_id = c("e1", "e2", "e3", "e4"),
std_text = c(
"I plan to batch my messages every morning from now on.",
"My manager encouraged me to protect focus time carefully.",
"I always slip back into back-to-back calls; the calendar is impossible.",
"Notification overload makes me drop async habits on busy days."
),
std_author = c("alice", "bob", "carol", "dave"),
theme_membership_Adoption = c(1L, 1L, 0L, 0L),
theme_membership_Resistance = c(0L, 0L, 1L, 1L)
)
# 3. Drive the provocateur loop with full transparency and run-state scaffolding.
result <- run_mode1(
data = my_corpus,
theme_set = my_themes,
config_path = "config.yaml", # methodology.mode = "reflexive_scaffold"
categories = c("counter_narrative", "disconfirming_evidence",
"absent_voice") # subset; defaults to all five
)
# result$reflection_log carries:
# - provocations[]: list of Provocation S3 objects (verified citations)
# - provocation_attempts: data.frame of every theme x category attempt
# (so coverage can distinguish "AI returned 0
# legitimately" from "category never attempted")
# - skipped_themes: themes the orchestrator bypassed (e.g., zero
# supporting entries) with explicit reasons
# - memos: reflexive notes (initially empty; you write them)
# 4. Reflexive memos
# are the shared re-engagement lever, available in every mode. Add memos
# in response to provocations that move you, then persist them.
result$reflection_log <- add_memo(
result$reflection_log,
body = "The AI's counter_narrative for 'Adoption' surfaces e3 + e4
from the candidate sample of non-theme entries. I had coded both under
Resistance, but re-reading them against Adoption: they describe failed
adoption attempts rather than refusal, which suggests Adoption and
Resistance may be one dimension, not two themes. Consider renaming or
merging.",
type = "theoretical",
linked_themes = "Adoption"
)
persist_memos(result$reflection_log, result$output_dir)Production recipe: loading a real corpus from config
The toy corpus above is for illustration. In practice you load a
standardized + preprocessed corpus from your SQLite database via
load_corpus_from_config(), then attach the
theme_membership_* columns from your external coding
workflow (NVivo / ATLAS.ti / MAXQDA export) before calling
run_mode1(). The loader runs the same path that
run_analysis() uses internally, so the corpus you give Mode
1 is bit-identical to what Modes 2 and 3 would see.
library(pakhom)
# 1. Parse the YAML config. methodology.mode must be "reflexive_scaffold".
cfg <- load_config("config.yaml")
# 2. Load + standardize + preprocess the corpus (load_and_combine_tables
# multi-table path, column detection, std_id/std_text/std_author
# standardization, text cleaning, and optional test_mode sampling all
# happen here -- no pakhom::: required).
corpus <- load_corpus_from_config(cfg)
# 3. Attach theme_membership_* columns from your external coding tool.
# Each column is an integer 0/1 indicator: which entries support
# which researcher-authored theme. (You export these from NVivo /
# ATLAS.ti / MAXQDA; pakhom never authors theme membership in Mode 1.)
ids_adoption <- readLines("nvivo_exports/adoption_ids.txt")
ids_resistance <- readLines("nvivo_exports/resistance_ids.txt")
corpus$theme_membership_Adoption <- as.integer(corpus$std_id %in% ids_adoption)
corpus$theme_membership_Resistance <- as.integer(corpus$std_id %in% ids_resistance)
# 4. Build the ThemeSet from your researcher-authored themes.
my_themes <- create_theme_set(list(
list(id = 1, name = "Adoption",
description = "Researcher-authored: async-communication adoption behaviors",
codes_included = c("async_routine", "daily_batching")),
list(id = 2, name = "Resistance",
description = "Researcher-authored: resistance to the new norms",
codes_included = c("skips_async", "tool_friction"))
))
# 5. Run the provocateur loop. Pass `config = cfg` (the already-parsed
# object) to avoid re-parsing the YAML, or pass `config_path =` if
# you'd rather hand off the path.
result <- run_mode1(data = corpus, theme_set = my_themes, config = cfg)What you get
A finalized run directory at outputs/<run-id>_M1/
containing:
-
run_metadata.json: methodology stamp + mode + run id + framework hash (NA for Mode 1) + timestamp -
rules/methodology_rules.md: the AC9 system prompt that governed every AI call -
reflection_log.json: full reflection log (provocations + attempts + skips + memos) -
provocations.csv: flat exportable provocation list (theme + category + cited quote + verification status) -
provocation_attempts.csv: the attempt-tracking matrix that drives the whole-corpus coverage check -
coverage_mode1.json:ProvocationCoverage(no_silent_skip headline + per-category attempt counts) -
themes.json: the researcher-authored themes archived for replay -
memos/<id>.md: one Markdown file per memo, with YAML frontmatter -
fabrication_log.csv: any fabricated provocation citations dropped during the run -
ai_decisions.jsonl: full audit trail of every AI call -
analysis_report.html: Mode 1 HTML report with transparency dashboards + per-theme provocations + memo timeline
When to use Mode 1
- You are conducting reflexive thematic analysis (Braun & Clarke 2022) and want AI to challenge your interpretation, not produce it.
- You have a constructionist or critical-realist epistemology where AI-generated themes would be epistemically incoherent with the methodology.
- Your codebook is small (under ~150 codes) so you can author it manually and want depth over scale.
- You take seriously Vikan et al. 2025’s finding that LLMs give only limited support for reflexive TA and that high-quality RTA still requires human interpretive engagement, and you want the package to keep you in the data via provocations rather than hand interpretation to the model.
Mode 2: Codebook Collaborative (the auto-pipeline)
In Mode 2 the AI proposes codes, then themes, and the researcher gates each at pause-points. This is the workflow most users coming from a manual coding tradition will recognize: the AI does the mechanical work, the researcher curates.
Pause points
By default run_analysis() will pause after progressive
coding (so you can review the codebook in CSV form) and again after
theme generation (so you can curate themes before correlations are
computed). At each pause, the pipeline exports a CSV; you edit in-place
and rename it (codebook_review.csv to
codebook_reviewed.csv); then re-run with
resume = TRUE.
You can also configure no pauses and let the pipeline run end-to-end, which is useful for batch and replay runs after the codebook is stable.
Worked example
library(pakhom)
# 1. Create a config (the wizard or programmatically). Methodology
# declaration is mandatory.
create_config(
methodology = "codebook_collaborative",
study_name = "Remote-work wellbeing forum study",
research_focus = "How do users describe the wellbeing effects of remote work?",
database_path = "corpus.db",
output_path = "config.yaml"
)
# 2. Run the full pipeline. Returns invisibly with the analytic_data
# + theme_set + correlations + insights + paths to the output dir.
result <- run_analysis("config.yaml")
# 3. If pause-points are enabled, run_analysis returns a status message
# and stops at the pause. Edit the exported CSV and re-run with
# resume = TRUE:
result <- run_analysis("config.yaml", resume = TRUE)
# 4. The Mode 2 report at result$output_dir/analysis_report.html
# carries the transparency dashboards + saturation curve + per-theme
# sentiment breakdown + correlation matrix + AI-synthesis executive
# summary.What you get
A finalized run directory at outputs/<run-id>_M2/
with:
- All universal transparency and run-state artifacts (run_metadata, methodology rules, fabrication_log, ai_decisions)
-
sentiment_scores.csv: per-entry sentiment + emotions + intensity -
codes.csv: the codebook (codes preserved as atomic leaves per C2; renamed fromconsolidated_codes.csv) -
themes.json: theme set with merge_history + supporting quotes -
theme_entries/: one CSV per theme with member entries -
correlations.csv: Spearman correlations + Bonferroni-adjusted p-values -
analysis_report.html: Mode 2 HTML report -
correlation_plot.png,theme_network.png: supporting visualizations
When to use Mode 2
- You are doing codebook TA, template TA, or any approach where AI-generated codes are an acceptable input to your interpretive work.
- You have a medium-to-large corpus (200+ entries) and the AI’s mechanical coding speed is genuinely useful.
- You are willing to gate the AI’s output at pause-points rather than accept it wholesale.
- You want IRR + saturation diagnostics and the audit trail as your defensibility argument.
The v2 theme algorithm
Mode 2’s theme-generation step uses the v2 multi-pass
clustering with label-after-clustering algorithm
(R/theme_algorithm_v2.R::generate_themes_multipass). The AI
sees the entire codebook at once, proposes a partition into clusters,
and either continues with another pass (grouping clusters further) or
declares convergence. After convergence, a dedicated labeling pass with
the whole tree visible assigns researcher-facing names +
descriptions.
The algorithm honors C-tenets 1 (AI decides when to stop – no
hardcoded pass count), 2 (codes preserved as atomic leaves), 3 (live
tracking per pass:
outputs/<run>/live/clustering_pass_<N>.json),
and 5 (label after clustering, never during).
Empirical evidence from validation (Mode 2 on a 250-entry online forum corpus):
| Run | Research focus | Codes | Substantive passes | v2 themes | Single-code | v1 baseline |
|---|---|---|---|---|---|---|
| 1 | narrow three-concept intersection | 40 | 1 | 6 | 0% | 69 (87% single-code) |
| 2 | broader single-concept focus | 47 | 1 | 10 | 0% | 117 |
| 6 | affect-oriented focus | 157 | 3 | 7 | 0% | 154 (92% single-code) |
In these three validation runs (n = 3 – indicative, not a performance guarantee) all landed in a 4-10 theme range with zero single-code themes. The Run 6 case (157 codes, then 3 substantive passes, then 7 themes with 2-3 subthemes each) demonstrates that v2 can produce a clean theme/subtheme hierarchy at realistic codebook scale. Single-code reduction is run-dependent, not a guaranteed invariant (C-tenet 1: the AI judges convergence; there are no count thresholds).
Dynamic clustering depth, the AI analyst, and the honesty layer
Clustering depth is the AI’s call, per study. The
number of passes and the depth of the theme/subtheme hierarchy are
emergent, not configured: a narrow, cohesive codebook may converge in a
single pass to a handful of flat themes, while a broad codebook may take
several passes into a deeper hierarchy. Flat output is not a defect and
deep output is not a goal – both are valid readings of different data
(C-tenet 1). pakhom never imposes a target shape, and it never combines
codes into new codes: every pass returns a partition of the
existing codes, and a theme is the union of the original codes
grouped under it (C-tenet 2). The themes it produces are a refinable
scaffold – enable the after_themes review point to inspect
and adjust the grouping before the report is built.
Named facets may disperse (Mode 2) or be guaranteed (Mode 3). In inductive Mode 2, a secondary facet you named in your research focus (say, “physical effects” within a broad lived-experience study) may not surface as its own theme: the codes that express it can legitimately disperse across the emotional and behavioral themes where they cohere more strongly. That is sound emergent grouping – the content is preserved in the codes and visible in the per-theme breakdowns – not a coverage failure. The Mode 2 report includes a research-question coverage section that maps each named facet to where it landed across the themes (marking dispersion explicitly as the valid outcome it is), so you can confirm your study addressed each facet without forcing a theme per facet. If you need guaranteed coverage of named facets, use Mode 3 (Framework Applied), where the facets are the framework constructs and every construct is reported.
The AI is the analyst; the package is its
calculator. Before coding, a Methodology Assistant articulates
a relevance criterion (injected into the coding prompt to keep coding
on-focus) and, for each numeric or timestamp column, decides which
computational primitives are an honest summary – a right-skewed
count is summarized by a median and tail measures, not a mean+SD; a
bounded ratio is not treated as an unbounded count. These choices are
free-form (the AI is never handed a fixed menu to pick from) and
archived, so the methodology decisions can be re-applied
deterministically on a re-run (via
config$study$inferred_methodology, which removes those AI
calls from the loop) – though the coding, sentiment and synthesis steps
still query the model and are not bit-reproducible.
The honesty layer. The report is built to be trustworthy under review:
- Metric provenance. Each metric is judged as a substantive measure of the phenomenon or as incidental source/platform metadata (upvotes, comment counts), and grouped accordingly, so platform reception is never silently read as prevalence or severity.
-
Small-n reliability. Spread and distribution-shape
statistics on small subthemes are marked as indicative: the analyst
supplies a per-column reliability floor (
min_reliable_n) and the per-subtheme table flags any spread/shape cell below it. Nothing is hidden – the value and its n are always shown; the threshold is the analyst’s judgement, not a fixed cutoff. -
No circular findings. A correlation between two AI codings
of the same text (an affect score and a theme-membership indicator) is
internal coding consistency, not an empirical association; such pairs
are excluded from the findings (and from the correlation plot’s
significance markers) but kept in the exported matrix with an
exclusion_reason, so the exclusion is auditable. - Saturation, honestly labelled. The coverage report distinguishes the entries coded, examined, and sampled, and the AI saturation arbiter judges convergence from the entries-coded growth curve rather than a fixed heuristic.
None of these introduce user-facing hardcoding: the AI judges per run, and the package renders those judgements transparently rather than classifying your data for you.
Choosing the right configuration
Once you’ve picked Mode 2 via
methodology_decision_aid(), the
configuration_selection_aid() function helps you set
expected outcomes + recommended review points based on your corpus
shape:
library(pakhom)
# Narrow intersection focus, 250-entry corpus:
configuration_selection_aid(
mode = "codebook_collaborative",
corpus_size = 250,
focus_shape = "narrow_intersection"
)
#> $expected_themes: [5, 8]
#> $expected_passes: 1
#> $recommended_review_points$after_coding: TRUE
#> $expected_wall_time_min: 11
#> $expected_api_spend_usd: 3The bracket is indicative, not a guarantee – it is derived from only three validation runs (above), so treat the numbers as rough expectations that will vary with your corpus and model. A narrow research focus tends to produce fewer codes per coded entry (more cohesive); a broad focus produces more codes and may require more clustering passes.
Mode 3: Framework Applied (apply a theoretical framework verbatim)
In Mode 3 you supply a theoretical framework (e.g., the Theory of Planned Behavior, COM-B, or the Theoretical Domains Framework). The AI applies it verbatim, coding entries with the framework’s constructs as labels, and flags entries that resist the framework as anomalies per the framework’s anomaly_handling policy.
Built-in frameworks
library(pakhom)
list_builtin_frameworks()
#> [1] "tpb" "comb" "tdf"
# Each is loadable by alias OR by file path
spec <- load_framework_spec("tpb")
print(spec)
#> FrameworkSpec: Theory of Planned Behavior
#> Epistemic stance: positivist
#> Anomaly policy: bracket
#> Constructs: 5
#> - attitude (Attitude toward the behavior): ...
#> - subjective_norm (Subjective norm): ...
#> - perceived_behavioral_control (Perceived behavioral control): ...
#> - intention (Behavioral intention): ...
#> - behavior (Behavior): ...
#> Citations: 2The three anomaly_handling policies
What happens to entries that don’t fit any framework construct
depends on the framework’s anomaly_handling field:
| Policy | Behavior |
|---|---|
bracket |
Entry coded as anomaly with a one-sentence reason;
framework NOT modified. Most positivist. |
extend |
Anomaly becomes a new construct (Vila-Henninger 2024 “abductive coding”); requires explicit researcher acceptance. |
revise |
Anomaly triggers modification of an existing construct’s definition; logged as framework revision. |
Worked example
library(pakhom)
# 1. Create a Mode 3 config -- framework is applied verbatim.
create_config(
methodology = "framework_applied",
framework_spec_path = "tpb", # built-in alias OR path to your YAML/JSON
study_name = "TPB analysis: async-communication adoption",
research_focus = "Behavioral intention -> async-communication behavior",
database_path = "corpus.db",
output_path = "config.yaml"
)
# 2. Run the pipeline. Internally:
# - load_framework_spec() -> validates the spec
# - archive_framework_spec() -> writes outputs/<run>/framework_applied.yaml
# + sha256 hash for replay-equivalence
# - run_metadata.json carries framework_name + framework_hash +
# framework_epistemic_stance + framework_anomaly_handling +
# framework_n_constructs
# - The Mode 3 HTML report renders a Framework Declaration section
# with the framework's name + sha256 + citations + epistemic stance +
# anomaly handling policy + full constructs table
result <- run_analysis("config.yaml")
# 3. The report at result$output_dir/analysis_report.html includes a
# new "Theoretical Framework (Mode 3 / AC4)" section. The
# framework_applied.yaml file alongside is byte-equivalent to the
# spec that was loaded -- a downstream replay using the same hash
# is provably the same framework.A note on Mode 3 + Anthropic
When mode = "framework_applied" AND
provider = "anthropic", the Anthropic Citations API path is
structurally precluded: forced tool_use schema (which Mode
3 requires to constrain coding to framework constructs) and the
Citations API output format are mutually exclusive on the same response.
The Mode 3 + Anthropic pipeline therefore relies on the verification
ladder’s DETECTION-only path (model_freeform + offline string match)
rather than the API’s PREVENTION layer.
The Mode 3 report renders an explicit footnote disclosing this constraint. Without the footnote, a reviewer reading “Model freeform (detection only)” in the transparency dashboard would reasonably wonder why the Anthropic prevention layer isn’t engaged, so the footnote makes the architectural reason explicit rather than letting it look like a bug.
A hybrid schema (constrained constructs paired with citation offsets) is a possible future direction.
When to use Mode 3
- You have a pre-existing theoretical framework you want to apply rigorously.
- You are conducting deductive coding, content analysis, or framework analysis (Ritchie & Spencer).
- The framework’s epistemic stance and anomaly handling policy are intentional methodological choices, not defaults.
Choosing among the three modes
| You are doing… | Mode |
|---|---|
| Reflexive thematic analysis (Braun & Clarke 2022); want AI to challenge, not produce | 1 |
| Codebook TA / template TA; want AI mechanical coding with researcher curation | 2 |
| Theoretical framework analysis (TPB, COM-B, TDF, your own) with deductive coding | 3 |
| Constructionist / critical realist epistemology; AI-as-author would be incoherent | 1 |
| Positivist or pragmatic epistemology; AI-as-coder is acceptable input | 2 or 3 |
| Small corpus (under ~150 codes); want depth | 1 |
| Medium-to-large corpus (200+ entries); want scale + audit trail | 2 |
| Pre-registered framework analysis | 3 |
| Your research focus differs from the corpus’s dominant signal | 1 (see “Mode 2 drift” below) |
Mode 2 drift on skewed-signal corpora
A re-validation on a 9,178-entry online discussion-forum corpus surfaced a recurring failure mode you should know about before choosing Mode 2:
When the corpus’s dominant signal is NOT the configured research focus, Mode 2 themes drift toward the corpus’s natural topic structure and away from the question you asked.
Concretely: the run configured a narrow three-concept research focus against a large general-interest forum’s posts and comments. The corpus’s actual dominant signal was the forum’s broad everyday affect, not that narrow intersection. Mode 2’s earlier clustering algorithm faithfully recovered the dominant signal – 417 themes, organized cleanly. But of those 417, only 2 named all three target concepts together, and only 1 had substantive mass (~56 entries). The load-bearing three-way interaction finding was buried in the long tail.
(Note: the algorithm has since been rewritten as multi-pass AI clustering with label-after-clustering, which surfaces a handful of themes rather than hundreds of single-code buckets. The narrative above describes a run of the earlier algorithm; under the current algorithm, the niche-intersection findings should surface as their own theme rather than vanishing in 417 single-code buckets. The indicative re-validation (n = 3, summarized earlier in this vignette) found the current algorithm does this; broader validation remains future work.)
This is not a bug in Mode 2 fundamentally. Any code-similarity-based
clustering – HAC, multi-pass AI partitioning, or other – is bottom-up:
it clusters by code-code similarity, not by research- question
relevance. If 80% of the corpus is about one dominant topic, 80% of the
themes will likely be about that topic, regardless of what your
research_focus says.
What to do about it
Audit your corpus’s signal balance first. If
research_focusis a niche intersection (e.g., a specific three-concept intersection), sample 100-200 random entries and ask: how many actually touch ALL three concepts? If the answer is “fewer than 10%”, Mode 2 will produce themes that contain your focus but won’t be organized around it.Switch to Mode 1 (Reflexive Scaffold) for the niche-focus case. In Mode 1 you author the themes; the AI doesn’t get a vote on theme generation. The themes are guaranteed to be organized around your
research_focus, and the AI’s role is restricted to provocateur loops + memo prompts that challenge what you wrote. Seevignette("getting-started")for the Mode 1 walkthrough.Refine
research_focusto match the dominant signal. If your data is mostly about one dominant topic, you can rewrite the question as “How does that dominant topic intersect with my secondary concepts when it does?” The themes will then be about the dominant topic with your secondary concepts as discriminating features – a publishable framing.Hybrid pipeline (intermediate effort). Run Mode 2 first, then manually curate the long-tail themes that touch your niche focus into a Mode 1 frame for a focused write-up. The Mode 2 audit log gives you cross-theme traceability for that curation.
Mode 2 is excellent when the research focus matches the corpus’s dominant signal (e.g., a topic-focused forum analyzed with a research focus on that same topic). It is not the right tool for niche-intersection focuses on broad corpora.
What every mode produces (the universal transparency layer)
Regardless of mode, every finalized pakhom run produces these universal transparency artifacts (per AC7):
-
Quote provenance (T0.1). Every AI-attributed
verbatim claim is verified through the verification ladder against the
cleaned analytic text the model coded (the raw platform text is
preserved in
original_text), and fabrications are dropped and logged tofabrication_log.csv. - Participant spread (T0.2). Every theme reports n_distinct_contributors, a Gini coefficient, and the top contributor’s share. A theme that looks prevalent but rests on one heavy poster gets a warning on the report.
- Whole-corpus coverage (T0.3). Modes 2/3 assert that every preprocessed entry reached the LLM (entry-level coverage; entries over the per-entry character cap are sent truncated, with the truncation measured and disclosed on the coverage card). Mode 1 asserts that there is no silent skip across themes and provocation categories. Both are surfaced via a coverage card on the HTML report.
These commitments are load-bearing: a finalized pakhom run that lacks
any of them is a transparency failure surfaced by
verify_run_integrity().
What each pipeline step transmits to the AI provider, and what a
finished run directory contains on disk, is documented in
vignette("data-flow").
Replay-equivalence and run_metadata.json
Every finalized run carries run_metadata.json with:
-
run_id+methodology_mode+mode_locked_at+is_finalized -
provider+model_primary+model_fast(which AI ran the analysis) -
config_hash(so a config drift between runs is detectable) -
framework_name+framework_hash+framework_*(Mode 3 only) -
mode1_categories_requested+mode1_n_themes_input(Mode 1 only) -
parent_run_id+mode_changed_from(when the run was forked from another) -
analysis_schema_version(output column schema;compare_runs()uses this to refuse cross-schema comparisons)
This metadata is the contract that makes pakhom runs
auditable and comparable across re-runs: two runs
against the same data + config + framework hash + provider produce
comparable (not bit-identical) artifacts. Cross-mode comparisons via
compare_runs() or compare_models() route off
these fields.
The two commitment families (AC1-AC10 and C1-C8)
The package commits to two distinct families of architectural promises:
- AC1-AC10 (mode-design commitments) govern what modes are and how methodology is enforced. They are the answer to “why three modes? why is there no default?” Documented in the README’s “architectural commitments” section.
- C1-C8 (rewrite-direction commitments) govern how the coding, clustering, and statistics algorithms think. They are the answer to “why doesn’t the AI just stop wherever the prompt says?” Surfaced in the README’s “rewrite-direction commitments” section.
The eighteen commitments together are the load-bearing contract. Every methodologist reading the package’s claims should be able to verify both sets against the code; anyone touching the algorithm layer should re-read both before changing behavior. The two families are orthogonal: mode-design changes (adding a fourth mode, say, which AC2 forbids) and algorithm changes (replacing single-pass HAC with multi-pass clustering) are independent.
For algorithm authors specifically, the C-commitments to watch:
| Commitment | Where it lives in code |
|---|---|
| C1: AI decides when to stop |
R/saturation_arbiter.R,
R/theme_algorithm_v2.R (multi-pass convergence) |
| C2: Codes preserved through clustering |
R/12_theme_data.R (Code S3),
R/theme_algorithm_v2.R::apply_partition (key flattening
only, no name mutation) |
| C3: Live tracking artifacts | R/live_tracking.R |
| C4: Dataset-agnostic |
R/16_report_helpers.R::.detect_metric_columns,
R/14_correlations.R
|
| C5: No catch-all buckets |
R/methodology_rules.R (inductive coding rules) |
| C6: Arbitrary research-question complexity |
R/01_config.R (no length validation on
research_focus) |
| C7: Mode-aware behavior |
R/18_pipeline.R, R/mode1_orchestrator.R,
framework-spec handling |
| C8: Publication-quality output shape |
R/16_report_helpers.R::.build_subtheme_summary_table,
R/17_report.R
|
If you contribute and aren’t sure whether a change violates a commitment, the rule of thumb is: does this change introduce a hardcoded number, a hardcoded column name, or a hardcoded mode assumption? If yes, you’re probably violating C1, C4, or C7. Open a PR with the commitment cited and discuss before merging.
Further reading
-
vignette("getting-started"): full step-by-step Mode 2 walkthrough -
?run_mode1: Mode 1 orchestrator API reference -
?run_analysis: Mode 2/3 orchestrator API reference -
?load_framework_spec: framework spec loading + built-in frameworks -
?add_memo: Mode 1 reflexive memo CRUD -
?compute_mode1_coverage: Mode 1 T0.3 coverage compute - Sarkar 2024 (CACM, Oct 2024), “AI Should Challenge, Not Obey”: Mode 1 motivation
- Jowsey et al. 2025 (PLOS One, doi:10.1371/journal.pone.0330217), the “Frankenstein” finding: motivation for the transparency layer
- Braun & Clarke 2022: reflexive TA foundation
- Vila-Henninger 2024: abductive coding (Mode 3
extendpolicy) - Lin & Corley 2025 (arXiv:2505.03105): methodology rules pattern (AC9)