Configuration
All behavior is controlled by a single config.yaml file. No defaults exist in code -- every value must be present in config or the application crashes with a clear error message.
Environment Variable Expansion
String values support ${VAR_NAME} syntax. The variable must be set in the environment (or in a .env file); missing variables cause a startup crash.
auth:
client_id: "${MSAL_CLIENT_ID}"
tenant_id: "${MSAL_TENANT_ID}"
Path Resolution
Path values (base_path, state_file_path, token_cache_path) that are relative are resolved against the config file's directory, not the process working directory.
# If config.yaml is at /home/user/m365-brain/config.yaml
# then this resolves to /home/user/m365-brain/vault/
storage:
local:
base_path: "./vault"
Full Reference
auth
Authentication settings for MSAL device code flow.
| Key |
Type |
Description |
client_id |
str |
Azure AD application (client) ID. Use ${MSAL_CLIENT_ID}. |
tenant_id |
str |
Azure AD directory (tenant) ID. Use ${MSAL_TENANT_ID}. |
scopes |
list[str] |
Microsoft Graph API permission scopes to request. |
token_cache_path |
str |
Path to the MSAL token cache file. Relative paths resolved against config directory. |
auth:
client_id: "${MSAL_CLIENT_ID}"
tenant_id: "${MSAL_TENANT_ID}"
scopes:
- "User.Read"
- "Mail.Read"
- "Calendars.Read"
- "Chat.Read"
- "ChannelMessage.Read.All"
- "Files.Read.All"
- "Sites.Read.All"
- "offline_access"
token_cache_path: "./state/token_cache.json"
service
Service-level settings.
| Key |
Type |
Description |
log_level |
str |
Logging level: DEBUG, INFO, WARNING, ERROR. |
There is no mode. It was a required string documented as accepting only "cli", validated by
nothing and read by nothing, so mode: "banana" loaded cleanly and changed nothing.
service:
log_level: "INFO"
storage
Storage backend selection and configuration.
| Key |
Type |
Description |
backend |
str |
Backend to use: "local" or "azure_blob". |
local |
LocalStorageConfig |
Local filesystem config. Required when backend is "local". |
azure_blob |
AzureBlobStorageConfig |
Azure Blob config. Required when backend is "azure_blob". |
storage.local
| Key |
Type |
Description |
base_path |
str |
Root directory for stored files. Relative paths resolved against config directory. |
storage.azure_blob
| Key |
Type |
Description |
connection_string |
str |
Azure Storage connection string. Use ${AZURE_STORAGE_CONNECTION_STRING}. |
container_name |
str |
Blob container name. Use ${AZURE_STORAGE_CONTAINER}. |
prefix |
str |
Path prefix within the container (e.g., "user1/" for per-user isolation). |
graph
Microsoft Graph API client settings.
| Key |
Type |
Description |
max_retries |
int |
Maximum retry attempts for failed requests (handles 429, 500, 502, 503, 504). |
backoff_base_ms |
int |
Base backoff duration in milliseconds. Doubles on each retry attempt. |
timeout_seconds |
int |
HTTP request timeout in seconds. |
max_pages |
int |
Maximum number of pages to follow when paginating results. Safety limit. |
graph:
max_retries: 3
backoff_base_ms: 2000
timeout_seconds: 30
max_pages: 100
state
Sync state persistence.
| Key |
Type |
Description |
state_file_path |
str |
Path to the JSON file storing delta links and sync timestamps. Relative paths resolved against config directory. |
state:
state_file_path: "./state/sync_state.json"
Per-extractor configuration. Each extractor has an enabled flag and extractor-specific settings.
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the email extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
folders |
list[str] |
Mail folders to sync. Valid values: Inbox, SentItems, Drafts, Archive, DeletedItems, JunkEmail. |
max_items_per_sync |
int |
Item budget per folder per cycle, sent verbatim as $top on the delta query. On a delta query $top caps the whole enumeration, not the page size: Graph returns at most this many messages and then reports the folder complete with a delta link, so an initial sync never picks up the remainder. Set it above the number of messages the folder holds. |
There is no time window on email. An initial sync (before any delta link exists) enumerates
the entire folder, however old. A receivedDateTime cutoff was tried and provably did nothing:
a fresh sync with state cleared wrote 1,062 messages older than its own cutoff across all seven
folders. Why it did nothing is unresolved — Microsoft documents
$filter=receivedDateTime ge|gt {value} as supported on a message delta, so "Graph does not
support it" is not the explanation, and our filter string or param plumbing may have been at fault.
The knob is gone rather than restored on the strength of a doc page; bringing it back needs one
measured round. Size max_items_per_sync against the whole folder, not against a window.
(extractors.calendar.lookback_days is unrelated and does work: calendarView takes a genuine
date range.)
max_items_per_sync is also not the only bound in force. graph.max_pages caps the same round in
a different unit — pages, not items — and typically reaches its limit first, because a delta round
pages at roughly ten items whatever $top says. The round resumes from its nextLink next cycle,
so this costs throughput rather than data, and graph.delta_max_pages_reached reports the item
count it stopped at so the two are comparable.
Delta parameters freeze after the first request. Graph encodes $select and $top into the
state token it returns, and the library deliberately does not resend them on a resume. So editing
max_items_per_sync — or picking up a $select widened in a library release — changes nothing for
a folder that already has a delta link. The values in force are the ones configured the day the
chain was created; --resync is what adopts new ones.
extractors:
email:
enabled: true
poll_interval_minutes: 3
folders: ["Inbox", "SentItems", "Archive"]
max_items_per_sync: 500
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the calendar extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
lookback_days |
int |
Number of days to look back for past events. Future events are always fetched up to 90 days ahead. |
extractors:
calendar:
enabled: true
poll_interval_minutes: 60
lookback_days: 365
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the Teams chats extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
max_messages_per_chat |
int |
Maximum number of messages to fetch per chat. |
extractors:
teams_chats:
enabled: true
poll_interval_minutes: 5
max_messages_per_chat: 200
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the Teams channels extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
extractors:
teams_channels:
enabled: false
poll_interval_minutes: 5
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the OneDrive extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
eager_convert_patterns |
list[str] |
Glob patterns for files to convert immediately on sync (e.g., ["*.docx", "Reports/*.xlsx"]). |
convertible_extensions |
list[str] |
File extensions eligible for document conversion. |
max_file_size_mb |
int |
Maximum file size in MB to download and convert. |
extractors:
onedrive:
enabled: false
poll_interval_minutes: 120
eager_convert_patterns: []
convertible_extensions: [".docx", ".pptx", ".xlsx", ".pdf", ".csv", ".txt", ".md", ".html"]
max_file_size_mb: 100
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the SharePoint extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
eager_convert_patterns |
list[str] |
Glob patterns for files to convert immediately on sync. |
convertible_extensions |
list[str] |
File extensions eligible for document conversion. |
max_file_size_mb |
int |
Maximum file size in MB to download and convert. |
extractors:
sharepoint:
enabled: false
poll_interval_minutes: 240
eager_convert_patterns: []
convertible_extensions: [".docx", ".pptx", ".xlsx", ".pdf", ".csv", ".txt", ".md", ".html"]
max_file_size_mb: 100
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the contacts extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
extractors:
contacts:
enabled: false
poll_interval_minutes: 1440
| Key |
Type |
Description |
enabled |
bool |
Enable or disable the directory extractor. |
poll_interval_minutes |
int |
Interval between syncs in worker mode. |
extractors:
directory:
enabled: false
poll_interval_minutes: 10080
converters
Document conversion settings for OneDrive and SharePoint file extractors.
converters.backends
Maps file types to conversion backends.
| Key |
Type |
Description |
pdf |
str |
Backend for PDF files. "markitdown" or "native". |
docx |
str |
Backend for Word documents. |
pptx |
str |
Backend for PowerPoint presentations. |
xlsx |
str |
Backend for Excel spreadsheets. |
csv |
str |
Backend for CSV files. |
json |
str |
Backend for JSON files. |
yaml |
str |
Backend for YAML files. |
image |
str |
Backend for image files. |
default |
str |
Fallback backend for unrecognized file types. |
| Key |
Type |
Description |
timeout_seconds |
int |
Maximum time for a single document conversion. |
max_file_size_mb |
int |
Maximum file size to attempt conversion. |
xlsx_max_rows_per_sheet |
int |
Row limit per sheet when converting Excel files. |
| Key |
Type |
Description |
extract_images |
bool |
Whether to extract images from documents. |
image_format |
str |
Output format for extracted images (e.g., "png"). |
image_max_dimension |
int |
Maximum dimension for extracted images. 0 means no limit. |
converters:
backends:
pdf: "markitdown"
docx: "markitdown"
pptx: "markitdown"
xlsx: "markitdown"
csv: "markitdown"
json: "native"
yaml: "native"
image: "native"
default: "native"
extraction:
timeout_seconds: 30
max_file_size_mb: 100
xlsx_max_rows_per_sheet: 500
media:
extract_images: false
image_format: "png"
image_max_dimension: 0