# How the Dual-Layer Data Architecture in Career-Ops Works: A Complete Guide

> Explore Career-Ops dual-layer data architecture. Learn how it separates user files from system logic for secure data and a stable automation codebase. Discover the complete guide.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: deep-dive
- Published: 2026-06-13

---

**Career-Ops implements a dual-layer data architecture that strictly separates user-owned files (CVs, profiles, application trackers) from system-owned logic (scripts, modes, templates), ensuring personal data survives automatic updates while the automation codebase evolves freely.**

The santifer/career-ops repository solves the upgrade dilemma through a dual-layer data architecture formalized in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md). This design creates an immutable boundary between files that belong to the candidate and files managed by the system. By enforcing this separation, Career-Ops guarantees that your personal data and customizations persist untouched even as evaluation scripts, scoring rules, and templates receive updates.

## Understanding the Two Layers

### User Layer (Immutable Personal Data)

Files in the user layer belong to the candidate and are protected from automatic updates. According to [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) (lines 5-22), this layer includes:

- [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) – Your personal markdown CV
- [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) – Personal identity, compensation targets, and constraints
- [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) – Custom archetypes, narrative positioning, and negotiation scripts
- [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) – The canonical application tracker (source of truth)
- Writing samples and saved job descriptions

The contract explicitly defines the protection rule: *“If a file is in the User Layer, no update process may read, modify, or delete it”* (DATA_CONTRACT.md#L70-L74).

### System Layer (Replaceable Automation Logic)

All remaining files constitute the system layer and can be safely replaced by new releases. As defined in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) (lines 27-35), this includes:

- [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) – Global scoring rules and shared utilities
- [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) – Evaluation mode instructions for single offers
- `merge-tracker.mjs` – Script that merges tracker additions
- `templates/*` – HTML templates used for PDF generation
- `docs/*` – Documentation and architecture guides

The governing rule states that system-layer files *“can be safely replaced with the latest version from the upstream repo”* (DATA_CONTRACT.md#L70-L74).

## How the Layers Interact in Practice

### Evaluation Pipeline

When processing a job description, the system reads user-layer files as the source of truth. The evaluation logic residing in system-layer mode files such as [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) and [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) processes your [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) and [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml), then writes outputs (reports, PDFs, and tracker entries) into user-layer directories including `reports/`, `output/`, and `batch/tracker-additions/`.

### Tracker Management

The canonical tracker at [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) lives in the user layer, meaning automation workers never edit it directly. Instead, they emit TSV lines into `batch/tracker-additions/`. The system-layer script `merge-tracker.mjs` then merges these additions into the user-owned file without overwriting existing entries, as documented in [`docs/ARCHITECTURE.md`](https://github.com/santifer/career-ops/blob/main/docs/ARCHITECTURE.md) (lines 92-99).

### Update Safety

When a new Career-Ops version releases, the update checker replaces only system-layer files. This guarantees that personal data and customizations in the user layer survive every upgrade, while new scoring rules in [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) or templates in [`templates/cv-template.html`](https://github.com/santifer/career-ops/blob/main/templates/cv-template.html) update automatically.

## Working with the Architecture

### Adding Application Entries

To safely add a new application without touching the canonical tracker directly:

```bash

# Write a TSV line to the batch folder – the system script will merge it.

cat <<EOF > batch/tracker-additions/001-companyX.tsv
001\t$(date +%F)\tCompanyX\tSenior Engineer\tEvaluated\t4.3/5\t✅\t[001](reports/001-companyX-$(date +%F).md)\tInitial evaluation
EOF

```

Then execute the merge script:

```bash

# Run the merge script (system-layer) – safe because it only updates the tracker.

node merge-tracker.mjs

```

Verify the update preserved your history:

```bash

# Verify that the user-layer tracker contains the new row.

cat data/applications.md

```

### Modifying Personal Archetypes

Update your personal definitions without fear of system overwrites:

```bash

# Edit modes/_profile.md – never touched by system updates.

sed -i 's/Backend Engineer/Data Engineer/g' modes/_profile.md

```

## Key Files by Layer

| Layer | File | Purpose |
|-------|------|---------|
| **User** | [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) | Candidate's markdown CV |
| **User** | [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) | Personal identity and compensation targets |
| **User** | [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) | Custom archetypes and negotiation scripts |
| **User** | [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) | Canonical application tracker (source of truth) |
| **System** | [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) | Global scoring rules and shared utilities |
| **System** | [`modes/oferta.md`](https://github.com/santifer/career-ops/blob/main/modes/oferta.md) | Evaluation mode instructions |
| **System** | `merge-tracker.mjs` | Merges TSV additions into the user-layer tracker |
| **System** | [`templates/cv-template.html`](https://github.com/santifer/career-ops/blob/main/templates/cv-template.html) | HTML template for PDF generation |

## Summary

- Career-Ops uses a dual-layer data architecture defined in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) to separate user data from system logic.
- **User Layer** files ([`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md), [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml), [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md)) are immutable to system updates and contain personal data.
- **System Layer** files ([`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md), `merge-tracker.mjs`, `templates/*`) can be replaced during updates without affecting user data.
- The `merge-tracker.mjs` script respects this boundary by appending to rather than overwriting [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md).
- This architecture ensures your job search history and personal profiles survive codebase evolution.

## Frequently Asked Questions

### What happens if I modify a system-layer file?

If you modify files like [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) or [`templates/cv-template.html`](https://github.com/santifer/career-ops/blob/main/templates/cv-template.html), those changes will be overwritten when you update Career-Ops to a new version. To preserve customizations, maintain your changes in user-layer files (such as [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md)) which are excluded from the update process.

### Can I move files between the user and system layers?

Moving files between layers violates the data contract and will break the update safety guarantees. The [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md) explicitly lists which paths belong to each layer (lines 5-35). Keep personal data only in the listed user-layer paths to ensure the update checker preserves your files.

### How does the tracker merge process prevent data loss?

The `merge-tracker.mjs` script (system layer) reads TSV additions from `batch/tracker-additions/` and appends them to [`data/applications.md`](https://github.com/santifer/career-ops/blob/main/data/applications.md) (user layer) without truncating the existing file. As documented in [`docs/ARCHITECTURE.md`](https://github.com/santifer/career-ops/blob/main/docs/ARCHITECTURE.md) (lines 92-99), this append-only approach ensures your historical application data remains intact even if the merge script itself receives updates.

### Are user-layer files ever read by the system?

Yes, system-layer scripts read user-layer files as inputs. For example, evaluation modes read [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) and [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) to generate personalized reports. However, the contract strictly prohibits system-layer processes from writing to, modifying, or deleting user-layer files, creating a read-only relationship that protects your data from corruption during automation.