# Markov analysis
Biblicus provides a Markov analysis backend that learns a directed, weighted state transition graph from sequences of
text segments in a corpus. It is an exploratory analysis tool that produces structured, inspectable artifacts:
- A set of inferred states with per-state exemplars.
- A directed, weighted transition graph between states.
- A per-item decoded path that shows how each item traversed the state graph.
- Optional GraphViz exports for visualization.
Markov analysis is configured using YAML configurations, validated strictly, and stored as versioned snapshot artifacts under the
corpus. It is designed for experimentation across segmentation strategies and observation encodings.
## Observation encoder configurability
The observation encoder configuration is fully configurable. For hybrid encoders, you can control:
- `observations.categorical_source`: which observation field supplies categorical labels
- `observations.numeric_source`: which observation field supplies the numeric scalar
This allows hybrid encodings to use fields other than the defaults (for example, `llm_summary` or `segment_index`)
without changing the pipeline code.
## Topic-driven observations
Markov analysis can run topic modeling over segments and use the resulting topic labels as categorical observations.
This is useful when you want topic buckets to act as the observation symbols.
The topic modeling configuration is embedded inside the Markov configuration:
```
schema_version: 1
segmentation:
method: sentence
topic_modeling:
enabled: true
configuration:
schema_version: 1
llm_extraction:
enabled: false
lexical_processing:
enabled: false
bertopic_analysis:
parameters: {}
observations:
categorical_source: topic_label
model:
family: categorical
n_states: 6
```
When enabled, the Markov observations include `topic_id` and `topic_label`. Setting
`observations.categorical_source` to `topic_label` makes the topic labels the categorical symbols
used by the Markov model.
Topic-driven runs emit extra debugging artifacts:
- `topic_modeling.json`: the full topic modeling report used for the run.
- `topic_assignments.jsonl`: per-segment topic assignments with the original segment text.
- `entity_removal.jsonl`: redacted segment text used for topic modeling (when enabled).
## What Markov analysis does
Markov analysis treats each item as a sequence:
1) Start from extracted text artifacts for a corpus item.
2) Segment the text into an ordered sequence (sentences, fixed windows, or provider-backed segmentation).
3) Convert each segment into an observation vector (categorical labels, numeric features, embeddings, or combinations).
4) Fit a hidden-state Markov model to learn:
- latent states,
- transition probabilities between states,
- and per-state emission distributions.
5) Decode a most-likely state sequence for each item and emit the state transition graph.
## Text extract segmentation
Text extract is a provider-backed segmentation strategy designed for long documents. Instead of asking the model to
re-emit the entire text with labels, Biblicus gives the model a virtual file and asks it to insert XML tags in-place.
The model uses **str_replace tool calls** (old_str/new_str pairs), which Biblicus applies in memory.
This pattern has two benefits:
1) The model only emits a small edit script, which is cheaper and faster than reprinting the full transcript.
2) The original text remains intact; validation can prove that only tags were inserted.
Biblicus uses XML-style tags (`...`) so the edits are well-formed and easy to validate deterministically.
After applying the tags, Biblicus parses **spans** (the tagged text ranges). Interstitial content remains available
in the marked-up string without forcing the model to cover every token.
Example snippet:
System prompt excerpt:
**System prompt (excerpt):**
```
You are a virtual file editor. Use the available tools to edit the text.
Interpret the word "return" in the user's request as: wrap the returned text with
... in-place in the current text.
Current text:
---
Greeting. Verification. Resolution.
---
```
User prompt:
**User prompt:**
```
Return the segments that represent contiguous phases in the text.
```
Input text:
**Input text:**
```
Greeting. Verification. Resolution.
```
Marked-up text:
**Marked-up text:**
```
Greeting. Verification. Resolution.
```
Structured data:
**Structured data (result):**
```
{
"marked_up_text": "Greeting. Verification. Resolution.",
"spans": [
{"index": 1, "start_char": 0, "end_char": 10, "text": "Greeting."},
{"index": 2, "start_char": 11, "end_char": 24, "text": "Verification."},
{"index": 3, "start_char": 25, "end_char": 37, "text": "Resolution."}
],
"warnings": []
}
```
## Run Markov analysis from the CLI
```
biblicus analyze markov --corpus corpora/ag_news_demo_2k --configuration configurations/markov/local-discovery.yml
```
Example span markup configuration (text extract provider-backed):
```
schema_version: 1
segmentation:
method: span_markup
span_markup:
client:
provider: openai
model: gpt-4o-mini
api_key: null
response_format: json_object
system_prompt: |
You are a virtual file editor. Use the available tools to edit the text.
Interpret the word “return” in the user’s request as: wrap the returned text
with ... in-place in the current text.
Use the str_replace tool to insert ... tags and the done tool when finished.
When finished, call done. Do NOT return JSON in the assistant message.
Rules:
- Use str_replace only.
- old_str must match exactly once in the current text.
- old_str and new_str must be non-empty strings.
- new_str must be identical to old_str with only and inserted.
- Do not include or inside old_str or new_str.
- Do not insert nested spans.
- If a tool call fails due to non-unique old_str, retry with a longer unique old_str.
- If a tool call fails, read the error and keep editing. Do not call done until spans are inserted.
- Do not delete, reorder, paraphrase, or label text.
Current text:
---
{text}
---
prompt_template: |
Return the segments that represent contiguous phases in the text.
Rules:
- Preserve original order.
- Do not add labels, summaries, or commentary.
- Prefer natural boundaries like greeting/opening, identity verification, reason for call,
clarification, resolution steps, handoff/escalation, closing.
- Use speaker turn changes as possible boundaries, but keep multi-turn exchanges together if they
form a single phase.
- Avoid extremely short fragments; merge tiny leftovers into a neighboring span.
model:
family: gaussian
n_states: 4
observations:
encoder: tfidf
```
When no extraction snapshot is provided, Markov analysis looks for a default recipe at
`corpora//recipes/extraction/default.yml` and builds or reuses the matching snapshot.
Recipes can include an optional `max_workers` field to control extraction concurrency. To keep
runs reproducible, pass an extraction snapshot explicitly:
```
biblicus analyze markov \
--corpus corpora/ag_news_demo_2k \
--configuration configurations/markov/local-discovery.yml \
--extraction-snapshot pipeline:RUN_ID
```
### Cascading configurations and CLI overrides
Markov analysis configurations support cascading composition. You can pass multiple `--configuration` files; later configurations override
earlier configurations via a deep merge:
```
biblicus analyze markov \
--corpus corpora/ag_news_demo_2k \
--configuration configurations/markov/base.yml \
--configuration configurations/markov/guided.yml
```
To override the composed configuration view from the command line, use `--config key=value` with dotted keys:
```
biblicus analyze markov \
--corpus corpora/ag_news_demo_2k \
--configuration configurations/markov/base.yml \
--configuration configurations/markov/guided.yml \
--config model.n_states=14
```
Omitted fields use the default values from the Markov analysis schema. Missing required fields remain hard errors.
## LLM observation cache
When `llm_observations.enabled` is true, Biblicus caches per-segment labels and summaries so you can rerun Markov analysis without
re-labeling the same text. The cache is keyed by the LLM client configuration (minus the API key), the prompt templates, and an
optional `cache_name`, so changing prompts or models creates a fresh cache automatically. Use `llm_observations.cache.cache_name`
to version caches for experiments.
Cache location:
```
.biblicus/cache/markov/llm-observations////
```
The cache is updated incrementally. If a prior run stopped partway through, rerunning the analysis will only label the missing
segments and continue.
To disable caching, set:
```
llm_observations:
cache:
enabled: false
```
## Output location and artifacts
Markov analysis output is stored under:
```
analysis/markov//
```
The snapshot directory contains a manifest and structured artifacts. The canonical output is `output.json`. Additional files
provide intermediate visibility, such as segments and observations used to fit the model. When enabled, GraphViz output
is written as `transitions.dot`.
## Working demo
The integration demo script is a working reference you can use as a starting point:
```
python scripts/markov_analysis_demo.py --corpus corpora/ag_news_demo_2k
```
If you want the script to download and ingest AG News into a fresh corpus directory, pass `--download` (this requires the
optional datasets dependency).
Markov analysis requires an optional dependency:
```
python -m pip install "biblicus[markov-analysis]"
```
The demo builds or reuses an extraction snapshot, executes Markov analysis with example configurations, and prints the resulting run
paths. Inspect the emitted `output.json` and graph artifacts to understand states and transitions.