# How career-ops Separates User-Layer and System-Layer Files: A Two-Tier Data Contract

> Discover how santifer/career-ops separates user-layer and system-layer files. Learn how its two-tier data contract protects user customizations during repository upgrades and enables seamless system updates.

- Repository: [Santiago Fernández de Valderrama/career-ops](https://github.com/santifer/career-ops)
- Tags: internals
- Published: 2026-08-22

---

**santifer/career-ops enforces a strict two-tier data contract that isolates personal user data in the user-layer from core system code in the system-layer, ensuring user customizations survive repository upgrades while allowing automated system updates.**

The open-source career management framework **career-ops** implements a rigorous separation of concerns between user-owned content and system-provided functionality. This architecture, defined in [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md), prevents accidental data loss during updates by distinguishing between files that serve as the source of truth for your career data and those that power the evaluation engine.

## Understanding the Two-Tier Architecture

### User-Layer Files (Immutable User Data)

These files constitute your personal data vault and are never overwritten automatically. According to the source code in [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md), this layer includes your canonical CV ([`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md)), personal profile configuration ([`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml)), custom archetypes ([`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md)), and tracker data such as [`article-digest.md`](https://github.com/santifer/career-ops/blob/main/article-digest.md), [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml), and contents of `data/`, `documents/`, `reports/`, `output/`, and `interview-prep/` directories. The system treats these as the **source of truth** for all user-visible output. When you define target roles, salary expectations, or personal narratives, the agent must write exclusively to these user-layer files.

### System-Layer Files (Auto-Updatable Engine)

All remaining infrastructure—including scripts, templates, and shared definitions—comprises the system layer. These files, including [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md), all `.mjs` scripts (such as `scan.mjs` and `set-status.mjs`), `templates/`, dashboard utilities in `dashboard/`, batch processing helpers in `batch/`, and instruction definitions like [`CLAUDE.md`](https://github.com/santifer/career-ops/blob/main/CLAUDE.md), [`CODEX.md`](https://github.com/santifer/career-ops/blob/main/CODEX.md), and [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md) itself, are updated automatically via `update-system.mjs` without affecting user data. The source code explicitly prohibits embedding user-specific content in these files to prevent overwrite collisions during upstream updates.

## Enforcement Rules That Prevent Data Loss

### Rule 1: Never Write User Data to System Files

The primary constraint requires that all personalization—including custom archetypes, compensation targets, and narratives—be persisted only to [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) or [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml). Editing system-layer files such as [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) with user-specific data violates this contract and risks destruction during automated updates.

### Rule 2: Isolate Procedural Customizations

Custom workflow preferences belong in [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md), a user-layer file instantiated from a template when missing. This separation allows you to override system behaviors without modifying the underlying engine code, preserving your workflow rules across system updates.

### Rule 3: Enable Automated System Updates

Files in the system-layer are explicitly designated as auto-updatable. The `update-system.mjs` script modifies only these files—such as core `.mjs` automation scripts and shared mode definitions—ensuring you receive bug fixes and features without manual intervention.

## Practical Implementation Examples

The following examples demonstrate correct versus incorrect file operations according to the career-ops data contract.

When adding a new target role, always modify the user-layer configuration:

```javascript
// ✅ Correct: edit config/profile.yml (user layer)
import yaml from 'js-yaml';
import fs from 'fs';
const profile = yaml.load(fs.readFileSync('config/profile.yml', 'utf8'));
profile.targets.push('Senior Data Engineer');
fs.writeFileSync('config/profile.yml', yaml.dump(profile));

// ❌ Incorrect: editing a system script
// This would break the system‑layer rule
// const script = fs.readFileSync('modes/_shared.md', 'utf8');
// // ... modify script with user data – DISALLOWED

```

To update the system-layer safely without touching user data:

```bash

# Updating system‑layer code via the built‑in updater (safe for system files)

node update-system.mjs apply   # touches only system‑layer files

# User‑layer files remain untouched

```

## File Reference: User vs. System Layers

| Tier | Important Files | Purpose |
|------|----------------|---------|
| **User‑Layer** | [`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md) | Canonical CV (source of truth). |
|  | [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) | Personal profile, salary targets, language settings. |
|  | [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) | User‑specific archetypes and narrative. |
|  | [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md) | Custom workflow rules (generated from template). |
|  | [`article-digest.md`](https://github.com/santifer/career-ops/blob/main/article-digest.md), [`portals.yml`](https://github.com/santifer/career-ops/blob/main/portals.yml), `data/*`, `documents/*`, `reports/*`, `output/*`, `interview-prep/*` | Tracker, documents, generated artifacts – all user‑owned. |
| **System‑Layer** | [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) | Shared mode definitions, never edited for a single user. |
|  | `*.mjs` scripts (e.g., `scan.mjs`, `set-status.mjs`) | Core engine logic. |
|  | `templates/*` | PDF/LaTeX templates, state definitions. |
|  | [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md), [`CLAUDE.md`](https://github.com/santifer/career-ops/blob/main/CLAUDE.md), [`CODEX.md`](https://github.com/santifer/career-ops/blob/main/CODEX.md), [`OPENCODE.md`](https://github.com/santifer/career-ops/blob/main/OPENCODE.md) | Agent and instruction definitions. |
|  | `dashboard/*`, `batch/*` | Automation and batch processing helpers. |

## Summary

- **santifer/career-ops** implements a strict two-tier data contract that isolates personal data from system code.
- **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), [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md), etc.) serve as the immutable source of truth and are never overwritten by automated updates.
- **System-layer files** (`.mjs` scripts, [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md), templates) power the evaluation engine and are maintained via `update-system.mjs`.
- **Enforcement rules** prohibit writing user data to system files, ensuring personalization survives repository upgrades.
- **Custom workflows** belong in [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md), keeping procedural overrides separate from core logic.

## Frequently Asked Questions

### What happens if I accidentally edit a system-layer file with my personal data?

If you embed user-specific content in system-layer files such as [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) or `.mjs` scripts, that data will be destroyed when `update-system.mjs` applies upstream changes. Always redirect personal edits to [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) or [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) to prevent data loss.

### How do I safely update the career-ops system without losing my data?

Run `node update-system.mjs apply` from the repository root. This command touches only system-layer files such as core scripts and shared definitions, leaving your user-layer files ([`cv.md`](https://github.com/santifer/career-ops/blob/main/cv.md), profile configurations, and tracker data) completely untouched.

### Which file should I edit to add custom job search rules?

Add custom workflow preferences to [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md), which is a user-layer file generated from a template if missing. This keeps your procedural modifications separate from the system engine while ensuring they persist through updates.

### Are user-layer files tracked in version control?

Yes. User-layer files are kept under version control but are excluded from bulk system updates. This allows you to track changes to your CV and profile over time while still receiving automated improvements to the underlying career-ops engine.