# How the Layered Agent Instruction Model Works in Career-Ops: A 3-Tier Architecture

> Discover how Career-Ops uses a 3-tier layered agent instruction model. Safely update system logic and personalize AI behavior across CLI tools with its hierarchical stack.

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

---

**Career-Ops implements a hierarchical instruction stack that separates system logic from user configuration through three distinct tiers—system, profile, and custom—ensuring safe updates and personalized AI behavior across multiple CLI tools.**

The open-source Career-Ops repository (santifer/career-ops) manages career optimization workflows through a sophisticated **layered agent instruction model** that keeps core functionality stable while allowing deep user customization. This architecture ensures that updates to the underlying engine never overwrite personal settings, creating a safe environment for tailoring AI behavior to individual career narratives and target roles.

## The Three Tiers of the Instruction Stack

The model organizes instructions into a hierarchical stack where higher layers override lower ones. Each tier serves a distinct purpose and resides in specific files within the repository.

### System Layer (modes/_shared.md)

The **system layer** provides the generic "brain" that powers every operational mode, including scoring algorithms, evaluation logic, job scanning, and PDF generation. Located in [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) and various `*.mjs` scripts, this tier loads first and establishes baseline behavior shared across all supported CLIs. According to the Career-Ops source code, this layer contains versioned core logic that updates automatically via the `update-system.mjs` utility, ensuring all users receive improvements to scoring and scanning without manual intervention.

### Profile Layer (modes/_profile.md, config/profile.yml)

The **profile layer** personalizes the user's narrative, professional archetypes, target roles, and salary goals. Configuration lives in [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md) and [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml), allowing users to override system defaults with personal preferences. When a value exists in the profile—such as `language.output: fr` for French prose—it masks the corresponding system default without modifying core files, enabling localized output while retaining the English-based scoring engine.

### Custom Layer (modes/_custom.md)

The **custom layer** handles procedural tweaks and workflow enforcements that fall outside system or profile concerns. Users create this layer by copying [`modes/_custom.template.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.template.md) to [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md), then appending rules like skipping specific job types or altering output formats. Applied last in the stack, this tier enables behavioral changes without touching shared core logic, allowing users to enforce domain-specific constraints such as automatically rejecting internship positions.

## How the Agent Dispatcher Orchestrates Prompt Assembly

The **agent dispatcher**, defined in [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md), maps each supported CLI (Claude, Codex, Opencode, Gemini, etc.) to a corresponding skill file (e.g., [`CLAUDE.md`](https://github.com/santifer/career-ops/blob/main/CLAUDE.md), [`CODEX.md`](https://github.com/santifer/career-ops/blob/main/CODEX.md)). Each skill file loads the three instruction tiers in strict sequence:

```

[system-layer] → [profile-layer] → [custom-layer] → [user query]

```

This **CLI-agnostic entry point** guarantees identical core behavior across different AI assistants while allowing each tool to supply wrapper-specific arguments. The final assembled prompt respects the **Data Contract** outlined in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md), ensuring that layers remain additive rather than destructive, with user queries always receiving the accumulated context of all previous tiers.

## Separation of Concerns and Safe Updates

The architecture enforces strict **separation of concerns** through file path segregation. The `update-system.mjs` script only modifies files listed under `SYSTEM_PATHS`, which includes [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) and `*.mjs` scripts, while explicitly excluding user files like [`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), and [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md).

This design implements an **override hierarchy** where profile and custom layers safely mask system defaults. When the updater runs, it preserves all personalization while refreshing the generic logic, eliminating the risk of update-driven configuration loss. Additionally, every layer terminates with a human-in-the-loop confirmation request, ensuring no automated process can bypass user approval for critical actions.

## Practical Implementation Examples

The following examples demonstrate how to interact with the layered model using different CLIs and configuration overrides.

Run a standard job evaluation using Opencode:

```bash
opencode run "career-ops evaluate https://example.com/job/123"

```

Override default language output via the profile layer by editing [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml):

```yaml
language:
  output: fr

```

The next execution will generate French prose while maintaining the English scoring core from the system layer.

Add a custom rule in [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md) to skip internship positions:

```markdown
if job.title contains "intern" then set status to SKIP

```

This custom layer instruction applies to all subsequent evaluations without modifying shared system files.

## Summary

- The Career-Ops **layered agent instruction model** uses three tiers: system ([`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md)), profile ([`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md), [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml)), and custom ([`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md)).
- The **agent dispatcher** in [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md) orchestrates prompt assembly by loading skill files that stack tiers in order: system, then profile, then custom.
- **Separation of concerns** ensures `update-system.mjs` only touches `SYSTEM_PATHS` files, protecting user customizations from automatic updates.
- The architecture is **CLI-agnostic**, providing identical behavior across Claude, Codex, Opencode, and Gemini through standardized skill files that reference the same instruction stack.

## Frequently Asked Questions

### What is the order of precedence in the Career-Ops instruction stack?

The instruction stack follows a strict loading sequence defined in the skill files referenced by [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md). The system layer loads first to establish baseline behavior, followed by the profile layer which overrides defaults with user-specific values, and finally the custom layer which applies last-minute procedural tweaks. When conflicts occur, higher layers mask lower layers, ensuring personal settings always take precedence over generic defaults.

### How does Career-Ops prevent system updates from overwriting my custom settings?

The updater script `update-system.mjs` strictly adheres to the `SYSTEM_PATHS` whitelist, which only includes core system files like [`modes/_shared.md`](https://github.com/santifer/career-ops/blob/main/modes/_shared.md) and `*.mjs` scripts. User files including [`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), and [`modes/_custom.md`](https://github.com/santifer/career-ops/blob/main/modes/_custom.md) reside outside these paths and remain untouched during updates. This architectural boundary, combined with the Data Contract defined in [`DATA_CONTRACT.md`](https://github.com/santifer/career-ops/blob/main/DATA_CONTRACT.md), guarantees that personal configurations persist across system upgrades.

### Can I use Career-Ops with different AI coding assistants?

Yes, the layered model is **CLI-agnostic** by design. The agent dispatcher in [`AGENTS.md`](https://github.com/santifer/career-ops/blob/main/AGENTS.md) maps each supported CLI—whether Claude, Codex, Opencode, or Gemini—to a specific skill file (e.g., [`CLAUDE.md`](https://github.com/santifer/career-ops/blob/main/CLAUDE.md), [`CODEX.md`](https://github.com/santifer/career-ops/blob/main/CODEX.md)). Each skill file references the same three-tier instruction stack, ensuring consistent behavior across different AI tools while accommodating CLI-specific wrapper arguments and invocation patterns.

### What file should I edit to change my target roles or language preferences?

Modify [`config/profile.yml`](https://github.com/santifer/career-ops/blob/main/config/profile.yml) for structured data like target roles, salary goals, and language output settings. For narrative personalization and professional archetype definitions, edit [`modes/_profile.md`](https://github.com/santifer/career-ops/blob/main/modes/_profile.md). Both files reside in the profile layer and automatically override corresponding system defaults without altering core logic. The system recognizes these changes immediately upon the next CLI invocation.