TermAl

Feature Brief: Territory Visualization

Backlog source: docs/bugs.md

The backlog context and implementation plan below were moved out of docs/bugs.md.

No territory visualization

Severity: High — the single biggest coordination gap in a multi-agent workflow.

A developer paired with an agent has immense leverage: one person can drive multiple agents across different parts of a codebase simultaneously. But that leverage collapses without coordination visibility. Today the user has to hold the full territorial picture in their head — which agent is working where, which files are in flight, whether two sessions are about to collide. That mental bookkeeping scales badly and is the first thing to break under load.

File edits are buried inside individual conversation streams. There is no cross-session view that answers “which agent last touched this file?”, “what is each agent working on right now?”, or “are two sessions about to collide on the same module?” The developer is the sole coordination layer, and the tool gives them nothing to coordinate with.

Why this matters more than most features:

Desired behavior:

Data sources:

Tasks:

Implementation Plan: Territory Visualization

This is the concrete delivery plan for the territory visualization feature.

Core insight

The developer paired with agents is the coordination layer. TermAl’s value scales with how many agents the developer can run concurrently, but that only works if the tool gives them a live picture of who is changing what. Territory visualization is not a dashboard — it is the coordination surface.

Goals

Non-goals for v1

Data model

Touch event

Every time an agent reads, writes, creates, or deletes a file, the backend records a touch:

struct Touch {
    file_path: String,
    session_id: String,
    agent: Agent,              // Claude, Codex, Gemini
    action: TouchAction,       // Read, Write, Create, Delete
    lines_added: u32,
    lines_removed: u32,
    message_id: String,        // for click-through to conversation
    timestamp: DateTime<Utc>,
}

enum TouchAction {
    Read,
    Write,
    Create,
    Delete,
}

Sources:

File aggregate

The territory index maintains a rolled-up summary per file:

struct FileTerritory {
    file_path: String,
    touches: Vec<Touch>,        // append-only log
    dominant_agent: Option<Agent>,
    agents_involved: HashSet<Agent>,
    sessions_involved: HashSet<String>,
    contested: bool,            // true if multiple agents have written
    total_writes: u32,
    total_lines_changed: u32,
    last_write: DateTime<Utc>,
    last_read: DateTime<Utc>,
    external_change_detected: bool,
}

Rules:

Directory rollup

Aggregate file-level data upward into directories so the tree view can show territory at any depth:

struct DirectoryTerritory {
    dir_path: String,
    dominant_agent: Option<Agent>,
    contested: bool,
    file_count: u32,             // files with any touches under this dir
    contested_file_count: u32,
    agents_involved: HashSet<Agent>,
}

This is computed on demand from the file index, not stored separately.

Git supplementation

The territory map is only useful if it is honest. Agent-tracked touches cover TermAl activity, but the developer also edits files in their editor, runs scripts, pulls from remote, etc. A periodic git poll fills that gap.

Working tree poll

A background task runs on a configurable interval (default: 5 seconds):

  1. Run git status --porcelain to get the list of modified, added, and deleted files in the working tree.
  2. For each changed file, check whether the territory index already has a recent touch that explains the change (i.e., a TermAl session wrote it within the last poll interval).
  3. Any file that changed but has no matching TermAl touch → mark as external_change_detected and record an External touch with no session or agent attribution.
  4. Files that were previously marked external but are no longer in git status output → clear the external flag (the change was committed or reverted).

Remote poll

A separate, less frequent background task (default: 60 seconds, configurable):

  1. Run git fetch --quiet to update remote tracking refs.
  2. Run git rev-list --count HEAD..@{upstream} to check if upstream has new commits.
  3. If upstream has diverged, optionally run git diff --name-only HEAD...@{upstream} to get the list of files that would change on pull.
  4. Surface these as upstream territory entries — files the remote has changed that the developer hasn’t pulled yet.

This does NOT auto-pull. It just makes the territory map aware that the ground has shifted.

Git poll constraints

Conflict detection

Contested files are the highest-signal output of the territory system. The backend should proactively detect and categorize conflicts:

Level 1 — File-level contest: Two or more agents have written to the same file. Low urgency; this is information, not necessarily a problem.

Level 2 — Active collision: Two sessions with active (running) turns are both writing to the same file right now. Higher urgency; one of them is likely about to create a merge conflict.

Level 3 — External desync: An agent wrote a file, and then an external change was detected on the same file before the agent’s changes were committed. The agent’s mental model of that file is now stale.

Each conflict level should surface differently in the UI (color intensity, icon, notification).

API

Territory snapshot

GET /api/territory

Returns the full territory map:

{
  "files": [
    {
      "filePath": "src/main.rs",
      "dominantAgent": "Claude",
      "agentsInvolved": ["Claude", "Codex"],
      "sessionsInvolved": ["session-1", "session-4"],
      "contested": true,
      "totalWrites": 12,
      "totalLinesChanged": 347,
      "lastWrite": "2026-03-10T14:22:00Z",
      "lastRead": "2026-03-10T14:25:00Z",
      "externalChangeDetected": false,
      "conflictLevel": 1
    }
  ],
  "conflicts": [
    {
      "filePath": "src/main.rs",
      "level": 1,
      "agents": ["Claude", "Codex"],
      "sessions": ["session-1", "session-4"],
      "description": "Both Claude and Codex have written to this file"
    }
  ],
  "summary": {
    "totalTrackedFiles": 23,
    "byAgent": {
      "Claude": { "files": 8, "linesChanged": 412 },
      "Codex": { "files": 17, "linesChanged": 891 }
    },
    "contestedFiles": 2,
    "externalChanges": 1,
    "activeConflicts": 0
  },
  "gitStatus": {
    "upstreamBehind": 3,
    "upstreamFiles": ["README.md", "Cargo.toml", "src/lib.rs"]
  }
}

Territory for a single file

GET /api/territory/{filePath}

Returns the full touch log for one file, including the click-through messageId for each touch. Useful for the detail drill-down.

Territory SSE

Territory updates should piggyback on the existing /api/events SSE stream. When the territory index changes (new touch, conflict detected, external change found), include a territory delta in the next SSE snapshot so the frontend stays live without polling.

UI

Territory tab

A new workspace tab type:

type WorkspaceTab =
  | // ... existing types
  | { id: string; kind: "territory" };

The tab renders a collapsible project tree with:

Summary bar

Always visible across all tabs (in the status area or header):

Claude: 8 files (412 lines) · Codex: 17 files (891 lines) · 2 contested · 1 external

Clicking the summary bar opens the territory tab. Conflict counts should pulse or highlight when a new conflict is detected.

Heatmap mode

A toggle in the territory tab that reranks the tree by activity intensity instead of alphabetical path order. Files with the most cross-agent churn float to the top. Useful for spotting hotspots when the project tree is large.

Conflict notifications

When a Level 2 (active collision) or Level 3 (external desync) conflict is detected, surface a non-blocking toast notification so the developer sees it even if they are not looking at the territory tab.

Implementation phases

Phase 1: touch tracking and server index

Phase 2: territory tab and summary bar

Phase 3: git supplementation

Phase 4: conflict detection and notifications

Testing plan

Backend:

Frontend:

Integration:

Acceptance criteria