# Plan: Load `~/.agents/AGENTS.md` as a global instruction file

## Goal

Read `~/.agents/AGENTS.md` in addition to the existing user-level (`~/.config/swival/AGENTS.md`) and project-level (`<base-dir>/AGENTS.md`) instruction files. This follows the same cross-agent convention already used for `~/.agents/skills/`.

## Current behavior

`load_instructions()` in `swival/agent.py:940-1039` reads AGENTS.md from two locations:

1. **User-level**: `<config_dir>/AGENTS.md` (i.e. `~/.config/swival/AGENTS.md`)
2. **Project-level**: `<base_dir>/AGENTS.md`

Both share a combined `MAX_INSTRUCTIONS_CHARS = 10_000` budget. User-level is read first and gets priority. Content is wrapped in a single `<agent-instructions>` block with `<!-- user: ... -->` and `<!-- project: ... -->` provenance comments.

## Proposed loading order

1. **User-level** (swival-specific): `~/.config/swival/AGENTS.md` (or `$XDG_CONFIG_HOME/swival/AGENTS.md`)
2. **Global** (cross-agent): `~/.agents/AGENTS.md`
3. **Project-level**: `<base-dir>/AGENTS.md`

Rationale for this ordering:
- Swival-specific user config wins over the generic cross-agent file (same precedence pattern as skills).
- Both global files come before project-level, so project instructions appear last (closest to the model's attention at the end of the block).
- Actually, **project-level should come last** because the existing plan-user-agents-md.md already established that user-level is prepended to project-level. Adding the cross-agent global file between them keeps the same relative ordering: user-specific > cross-agent global > project.

Wait — reconsider. The current order puts user-level *before* project-level, meaning user content gets budget priority but appears earlier in the text. Project content appears last. This is the established pattern.

The cross-agent `~/.agents/AGENTS.md` is a personal global file (like user-level) but less specific to swival. Placing it between user-level and project-level means:
- Swival-specific personal prefs (`~/.config/swival/AGENTS.md`) come first
- Cross-agent personal prefs (`~/.agents/AGENTS.md`) come second
- Project rules come last

This mirrors the skills precedence: `.swival/skills/` > `.agents/skills/` > `--skills-dir` > `~/.config/swival/skills/` > `~/.agents/skills/`.

## Budget sharing

All three levels share the existing `MAX_INSTRUCTIONS_CHARS = 10_000` budget. They are read in order; each consumes from the remaining budget. A large user-level file can starve both the global and project files. This is documented and intentional — users control all three files.

No new budget constant is needed.

## Provenance comments

Each level gets an HTML comment identifying the source:

```
<agent-instructions>
<!-- user: /home/user/.config/swival/AGENTS.md -->
...user content...

<!-- global: /home/user/.agents/AGENTS.md -->
...global content...

<!-- project: /home/user/myproject/AGENTS.md -->
...project content...
</agent-instructions>
```

The new level uses the tag `<!-- global: ... -->` to distinguish it from `<!-- user: ... -->` (swival-specific).

## Changes

### 1. `swival/agent.py` — update `load_instructions()`

Insert a new block between the user-level and project-level reads (after line 1011, before line 1013):

```python
# Global cross-agent AGENTS.md (~/.agents/AGENTS.md)
global_agents_path = Path.home() / ".agents" / "AGENTS.md"
if global_agents_path.is_file() and budget > 0:
    try:
        file_size = global_agents_path.stat().st_size
        with global_agents_path.open(encoding="utf-8", errors="replace") as f:
            global_content = f.read(budget + 1)
    except OSError:
        if verbose:
            fmt.info(f"Skipped unreadable {global_agents_path}")
    else:
        if len(global_content) > budget:
            global_content = (
                global_content[:budget]
                + f"\n[truncated — global AGENTS.md exceeds {budget} character limit]"
            )
        budget -= len(global_content)
        if verbose:
            fmt.info(
                f"Loaded AGENTS.md ({file_size} bytes) from {global_agents_path.parent}"
            )
        agent_parts.append(f"<!-- global: {global_agents_path} -->\n{global_content}")
        loaded.append(str(global_agents_path))
```

The pattern is identical to the existing user-level block, just with a different path and `<!-- global: -->` tag.

### 2. Testable seam

`Path.home()` is a classmethod with global side effects — patching it in conftest would break unrelated tests (we learned this during the global skills work). Two options:

**Option A**: Extract a helper like `_global_agents_md_path()` and patch it in tests. This is the same pattern used for `_global_skill_dirs()`.

**Option B**: Pass the path as a parameter to `load_instructions()`. But the function signature already has `config_dir` for the swival-specific path; adding another optional path parameter feels awkward.

**Recommendation**: Option A. Add a small helper:

```python
def _global_agents_md_path() -> Path:
    """Return the cross-agent global AGENTS.md path (testable seam)."""
    return Path.home() / ".agents" / "AGENTS.md"
```

Tests patch `swival.agent._global_agents_md_path` to return a path under `tmp_path`.

### 3. `tests/conftest.py` — add autouse isolation

Add a fixture that patches `_global_agents_md_path` to return a nonexistent path, preventing real `~/.agents/AGENTS.md` from leaking into tests:

```python
@pytest.fixture(autouse=True)
def _isolate_global_agents_md(monkeypatch):
    """Prevent tests from picking up real ~/.agents/AGENTS.md."""
    monkeypatch.setattr(
        "swival.agent._global_agents_md_path",
        lambda: Path("/nonexistent/.agents/AGENTS.md"),
    )
```

### 4. `tests/test_instructions.py` — new tests

New `TestGlobalAgentsMd` class:

| Test | Description |
|------|-------------|
| `test_global_only` | Only `~/.agents/AGENTS.md` exists; appears in `<agent-instructions>` with `<!-- global: -->` comment |
| `test_all_three_levels` | All three files exist; order is user → global → project |
| `test_global_and_project_no_user` | Global + project, no user-level; both appear in correct order |
| `test_user_exhausts_budget_starves_global` | Large user-level file leaves no budget for global or project |
| `test_user_and_global_exhaust_budget` | User + global together exceed budget; project gets nothing |
| `test_global_unreadable_skipped` | Unreadable global file is skipped; other levels still load |
| `test_global_empty` | Empty global file doesn't crash; other levels load normally |
| `test_no_instructions_skips_global` | `--no-instructions` skips all three levels |

### 5. `docs.md/customization.md` — update docs

Update the AGENTS.md section (lines 77-88) to document three levels:

```markdown
### AGENTS.md

Loaded from up to three locations, in this order:

1. **User-level**: `~/.config/swival/AGENTS.md` (or `$XDG_CONFIG_HOME/swival/AGENTS.md`)
2. **Global cross-agent**: `~/.agents/AGENTS.md`
3. **Project-level**: `<base-dir>/AGENTS.md`

All are optional. Content is concatenated inside a single `<agent-instructions>` block
sharing a combined 10,000 character budget. Files are read in the order shown; earlier
files get budget priority.

The user-level file is for swival-specific personal conventions. The global file
(`~/.agents/AGENTS.md`) follows the cross-agent standard shared with OpenCode,
OpenHands, and similar tools — put conventions here that should apply regardless
of which agent you're using. The project-level file is for project-specific rules.
```

### 6. Docstring update

Update the `load_instructions()` docstring to mention three levels instead of two.

### 7. `swival/agent.py:1902` — update `--no-instructions` help text

The current help string says:

```
Don't load CLAUDE.md or AGENTS.md from the base directory or user config directory.
```

Update to mention the cross-agent global path:

```
Don't load CLAUDE.md or AGENTS.md from the base directory, user config directory, or ~/.agents/.
```

### 8. `docs.md/usage.md:154` — update `--no-instructions` description

Current text: `--no-instructions` prevents loading `CLAUDE.md` and `AGENTS.md` from both the base directory and the user config directory.

Update to mention all three sources.

### 9. `docs.md/reports.md:75` — update `instructions_loaded` description

Current text mentions "the user-level `AGENTS.md` from `~/.config/swival/` and the project-level files". Update to also mention `~/.agents/AGENTS.md`.

### 10. Rebuild generated HTML pages

Run `make website` to regenerate `docs/pages/customization.html`, `docs/pages/usage.html`, `docs/pages/reports.html`, and any other pages derived from the updated markdown sources. These are checked-in generated files that must stay in sync.

## Files touched

| File | Change |
|------|--------|
| `swival/agent.py` | Add `_global_agents_md_path()` helper, add global read block in `load_instructions()`, update docstring, update `--no-instructions` help text |
| `tests/conftest.py` | Add `_isolate_global_agents_md` autouse fixture |
| `tests/test_instructions.py` | New `TestGlobalAgentsMd` class (8 tests) |
| `docs.md/customization.md` | Update AGENTS.md section to document three levels |
| `docs.md/usage.md` | Update `--no-instructions` description to mention all three sources |
| `docs.md/reports.md` | Update `instructions_loaded` description to mention `~/.agents/AGENTS.md` |
| `docs/pages/*.html` | Rebuild via `make website` |

## Not in scope

- Scanning for `AGENTS.md` in ancestor directories (directory walking)
- A `--no-global-instructions` flag to skip only the global file — just don't create it
- User-level `CLAUDE.md` (separate decision)
- Merging or deduplication across levels — content is concatenated as-is
