Skip to main content

ADR-146: Unified Task ID Strategy for Multi-Project Environments

Status

PROPOSED - February 2, 2026

Context

Two Incompatible Task ID Systems

CODITECT currently operates two task identification systems that have evolved independently and cannot interoperate:

1. Track Nomenclature (ADR-054): Track.Section.Task[.Subtask] format

  • Examples: A.9.1.3, H.8.1.6, C.14.1.1
  • Used in: PILOT track files, governance hooks, session logs, CLAUDE.md
  • Enforced by: task-id-validator.py PreToolUse hook
  • Regex pattern: ^[A-Z]{1,2}\.\d+\.\d+(\.\d+)?:\s*
  • Scope: Single project (PILOT), no project prefix, no tenant awareness

2. Work Item Hierarchy (/project command): E{NNN}-T{NNN} format

  • Examples: E001-T001, E003-T042
  • Used in: scripts/work_items.py, sessions.db task_tracking table
  • Scope: Per-project task queuing, but no track context (which domain? which agent?)

Problems

ProblemImpactAffected Component
PILOT track tasks (A.9.1.3) have no project prefixCannot reference tasks across projectsSession logs, /cxq queries
Work items (E001-T001) have no track contextCannot determine domain or agentAgent routing, CEF pack selection
No project namespacingCustomer projects collide with PILOT IDsMulti-tenant cloud sync
task_id_validator.py has no project awarenessCannot validate cross-project referencesGovernance hooks
Session logs mix task IDs from multiple projectsAmbiguous history when reviewing past work/cxq, session-log-sync.py
Cloud sync requires globally unique IDsA.9.1.3 exists in both PILOT and customer projectscloud_sync_client.py, api.coditect.ai
No migration path between E-T and track formatsTwo parallel systems persist indefinitelywork_items.py, track files

Constraints

  1. Backward compatibility is mandatory. Thousands of existing task references in session logs, track files, and CLAUDE.md use bare A.9.1.3 format. These must remain valid.
  2. Human readability. Task IDs appear in tool call descriptions, session logs, and conversation. UUIDs are rejected.
  3. CLI friendliness. Task IDs are typed in terminal commands. They must be short and unambiguous.
  4. Multi-tenant isolation. Customer task IDs must never leak across tenant boundaries in cloud sync.
  5. Incremental adoption. The migration must be phased so existing workflows are not disrupted.

Decision

We adopt a Unified Task ID Format with optional project prefix, project context resolution, a bridge from E-T format, and tenant-scoped cloud sync identifiers.

1. Unified Task ID Format

Grammar:

unified_task_id = [project_prefix ":"] track_id
project_prefix = ALPHA (ALPHA | DIGIT | "-")* # 1-24 chars, case-insensitive
track_id = track "." section "." task ["." subtask]
track = [A-Z]{1,2} # A-N, O-AA, AB-AK
section = DIGIT+
task = DIGIT+
subtask = DIGIT+

Display format: [project:]track.section.task[.subtask]

Examples:

Task IDMeaning
A.9.1.3PILOT task (default project, backward compatible)
PILOT:A.9.1.3Same task, explicit project prefix
OPS-AC:H.9.2.1Org project "artifact-cleanup", track H, section 9, task 2, subtask 1
CUST-001:A.3.1.2Customer 001 project, track A, section 3, task 1, subtask 2
WEBAPP:B.2.4Project "WEBAPP", track B, section 2, task 4
PILOT:AA.5.2.1PILOT project, PCF Business track AA, section 5, task 2, subtask 1

Project prefix rules:

  • 1 to 24 characters, alphanumeric plus hyphens
  • Case-insensitive (stored uppercase, displayed uppercase)
  • Must be registered in the project registry before use
  • Reserved prefixes: PILOT, SYSTEM, INTERNAL

2. Project Context Resolution

When a task ID has no project prefix, the system resolves the project context through a deterministic fallback chain:

1. Explicit prefix in task ID        → Use that project
2. $CODITECT_PROJECT env var → Use env var value
3. .coditect-project file in cwd → Use file contents
4. Default → "PILOT"

Resolution logic (pseudocode):

def resolve_project(task_id: str) -> tuple[str, str]:
"""Returns (project_id, track_task_id)."""
if ":" in task_id:
project, track_task = task_id.split(":", 1)
return (project.upper(), track_task)

# Fallback chain
project = os.environ.get("CODITECT_PROJECT")
if not project:
project_file = Path.cwd() / ".coditect-project"
if project_file.exists():
project = project_file.read_text().strip()
if not project:
project = "PILOT"

return (project.upper(), task_id)

Environment variable:

# Set in shell profile or per-session
export CODITECT_PROJECT=PILOT # Default for CODITECT framework work
export CODITECT_PROJECT=OPS-AC # For artifact-cleanup project
export CODITECT_PROJECT=CUST-001 # For customer 001 project

Project file (.coditect-project):

PILOT

This file is checked into each project repository root, providing automatic project context when cd-ing into the project directory.

3. Project Registry

A project registry tracks all known projects and their allowed tracks. The authoritative registry is stored in org.db (Tier 2) as defined in ADR-144. A local read-only cache file is auto-generated from org.db for fast CLI lookups.

Authoritative source: org.db tables projects and project_tracks (ADR-144) Local cache: ~/.coditect-data/project-registry.json (auto-generated, read-only)

Schema:

{
"version": "1.0.0",
"projects": {
"PILOT": {
"name": "CODITECT PILOT",
"description": "Core framework development",
"tracks": ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N"],
"tenant_id": null,
"created": "2025-06-01T00:00:00Z",
"status": "active"
},
"OPS-AC": {
"name": "Artifact Cleanup Operations",
"description": "Operational artifact cleanup project",
"tracks": ["C", "H", "J"],
"tenant_id": null,
"created": "2026-02-01T00:00:00Z",
"status": "active"
},
"CUST-001": {
"name": "Customer 001 - Acme Corp",
"description": "Acme Corp deployment project",
"tracks": ["A", "B", "C", "D", "E"],
"tenant_id": "tenant_acme_001",
"created": "2026-03-01T00:00:00Z",
"status": "active"
}
}
}

Validation rules:

  • A task ID's track letter must exist in the project's tracks list
  • Projects with a tenant_id are isolated: their tasks are only visible within that tenant
  • New projects are added via /project-new <name> command or manual registry edit

4. Work Item Bridge (E-T to Unified Format)

The existing E{NNN}-T{NNN} format is bridged to the unified format through a mapping table.

Mapping approach:

E-T ComponentUnified Equivalent
E{NNN} (Epic)Maps to a project_id in the registry
T{NNN} (Task)Maps to a sequential task number, recorded with track context

Bridge table (in sessions.db):

CREATE TABLE IF NOT EXISTS work_item_bridge (
et_id TEXT PRIMARY KEY, -- e.g., "E001-T001"
unified_id TEXT NOT NULL, -- e.g., "PILOT:A.9.1.3"
project_id TEXT NOT NULL, -- e.g., "PILOT"
track TEXT NOT NULL, -- e.g., "A"
created_at TEXT DEFAULT (datetime('now')),
migrated_at TEXT, -- When migrated from E-T to unified
UNIQUE(unified_id)
);

Transition in work_items.py:

  • Phase 1: work_items.py accepts both E001-T001 and PILOT:A.9.1.3 as task identifiers
  • Phase 2: New tasks created via work_items.py default to unified format
  • Phase 3: E001-T001 display format shows unified equivalent in parentheses: E001-T001 (PILOT:A.9.1.3)
  • Phase 4: E-T format deprecated; work_items.py emits deprecation warning when E-T format is used

Example transition:

# Phase 1 - Both formats accepted
python3 scripts/work_items.py start E001-T001 # Legacy (works)
python3 scripts/work_items.py start PILOT:A.9.1.3 # Unified (works)

# Phase 2 - New tasks use unified format
python3 scripts/work_items.py create --project PILOT --track A --section 9 --task 2

# Phase 3 - Display shows both
python3 scripts/work_items.py show E001-T001
# Output: E001-T001 (PILOT:A.9.1.3) - Backend API migration [completed]

# Phase 4 - Deprecation warning
python3 scripts/work_items.py start E001-T001
# WARNING: E-T format is deprecated. Use unified format: PILOT:A.9.1.3

5. Cloud Sync Global Uniqueness

Cloud task IDs incorporate tenant namespacing for guaranteed global uniqueness.

Cloud task ID format: {tenant_id}/{project_id}/{track}.{section}.{task}[.{subtask}]

Examples:

Local DisplayCloud ID
A.9.1.3az1ai/PILOT/A.9.1.3
PILOT:A.9.1.3az1ai/PILOT/A.9.1.3
OPS-AC:H.9.2.1az1ai/OPS-AC/H.9.2.1
CUST-001:A.3.1.2tenant_acme_001/CUST-001/A.3.1.2

Properties:

  • Collision impossible: Tenant + project + track path guarantees uniqueness
  • Tenant isolation: Customer tasks are prefixed with their tenant_id, not az1ai
  • Local shortening: Users see CUST-001:A.3.1.2, never the full cloud ID
  • API routing: Cloud API uses tenant_id from the path for multi-tenant isolation via django-multitenant

Cloud sync payload:

{
"cloud_task_id": "az1ai/PILOT/A.9.1.3",
"local_task_id": "PILOT:A.9.1.3",
"project_id": "PILOT",
"tenant_id": "az1ai",
"track": "A",
"section": 9,
"task": 1,
"subtask": 3,
"status": "in_progress",
"updated_at": "2026-02-02T14:30:00Z"
}

6. Governance Hook Updates

task-id-validator.py (PreToolUse)

The existing regex pattern ^[A-Z]{1,2}\.\d+\.\d+(\.\d+)?:\s* is extended to accept an optional project prefix:

Updated pattern:

# Old pattern (ADR-054 only)
TASK_ID_PATTERN = re.compile(r'^[A-Z]{1,2}\.\d+\.\d+(\.\d+)?:\s*')

# New pattern (ADR-146 unified)
TASK_ID_PATTERN = re.compile(
r'^(?:([A-Z][A-Z0-9-]{0,23}):)?' # Optional project prefix (group 1)
r'([A-Z]{1,2})' # Track letter(s) (group 2)
r'\.(\d+)' # Section number (group 3)
r'\.(\d+)' # Task number (group 4)
r'(?:\.(\d+))?' # Optional subtask (group 5)
r':\s*' # Colon separator
)

Enhanced validation flow:

def validate_task_id(description: str) -> tuple[bool, str]:
"""Validate task ID in tool description.

Returns (is_valid, message).
"""
match = TASK_ID_PATTERN.match(description)
if not match:
return (False, "Missing or invalid task ID format")

project_prefix = match.group(1) # May be None
track = match.group(2)

# Resolve project context
if project_prefix:
project = project_prefix.upper()
else:
project = os.environ.get("CODITECT_PROJECT", "PILOT")

# Validate project exists in registry (if registry available)
registry = load_project_registry()
if registry and project not in registry["projects"]:
return (False, f"Unknown project: {project}")

# Validate track is allowed for this project
if registry and track not in registry["projects"][project]["tracks"]:
return (False, f"Track {track} not registered for project {project}")

return (True, f"Valid: {project}:{track}.{match.group(3)}.{match.group(4)}")

Backward compatibility: Bare A.9.1.3: still passes validation. The project prefix is resolved from context, defaulting to PILOT.

task-tracking-enforcer.py (PreToolUse:TodoWrite)

Updated to:

  • Accept unified format in TodoWrite content
  • Resolve project context for track validation
  • Pass project context to downstream task-plan-sync.py

7. Session Log Format

Session log entries gain project context in the task ID bracket:

Current format:

### 2026-02-02T14:30:00Z - [A.9.1.3] Backend API migration

**Author:** Claude (Opus 4.5)

- Completed database migration for user model

Updated format (with explicit project):

### 2026-02-02T14:30:00Z - [PILOT:A.9.1.3] Backend API migration

**Author:** Claude (Opus 4.5)

- Completed database migration for user model

Rules:

  • When $CODITECT_PROJECT is PILOT (default), the PILOT: prefix is optional in session logs for readability
  • When working in a non-PILOT project, the project prefix is required
  • /session-log command automatically prepends the resolved project context

8. Regex Quick Reference

ContextPatternExample Match
Bare track ID (ADR-054 compat)^[A-Z]{1,2}\.\d+\.\d+(\.\d+)?$A.9.1.3
Unified with colon (tool desc)^(?:[A-Z][A-Z0-9-]*:)?[A-Z]{1,2}\.\d+\.\d+(\.\d+)?:\s*PILOT:A.9.1.3: Do thing
Cloud ID^[a-z0-9_-]+/[A-Z][A-Z0-9-]*/[A-Z]{1,2}\.\d+\.\d+(\.\d+)?$az1ai/PILOT/A.9.1.3
Legacy E-T^E\d{3}-T\d{3}$E001-T001
Session log bracket\[(?:[A-Z][A-Z0-9-]*:)?[A-Z]{1,2}\.\d+\.\d+(?:\.\d+)?\][PILOT:A.9.1.3]

Consequences

Positive

  1. Unified identification. One format covers all contexts: local development, cross-project references, cloud sync, and multi-tenant isolation.
  2. Full backward compatibility. Every existing bare task ID (A.9.1.3) remains valid and resolves to PILOT:A.9.1.3 by default.
  3. Human readable. CUST-001:A.3.1.2 is immediately parseable by humans. No UUIDs, no opaque identifiers.
  4. CLI friendly. Short enough to type; colon separator is unambiguous and shell-safe.
  5. Track context preserved. Unlike E-T format, unified IDs always carry track information, enabling agent routing and CEF pack selection.
  6. Collision-free cloud sync. Tenant/project/track path structure makes collisions mathematically impossible.
  7. Incremental adoption. Four-phase migration allows gradual transition with no breaking changes at any phase.

Negative

  1. Additional complexity. Project context resolution adds a fallback chain that must be understood and debugged.
  2. Registry maintenance. The project registry file must be kept in sync with actual projects. Stale entries cause validation failures.
  3. Hook performance. Loading and parsing project-registry.json on every tool call adds latency. Mitigated by caching the registry in memory per session.
  4. E-T deprecation timeline. Existing scripts and documentation referencing E-T format require updates during Phase 3-4.

Neutral

  1. Session log format change. Adding PILOT: prefix to session logs is optional for PILOT project work, avoiding unnecessary noise.
  2. Cloud ID opacity. Users never see the full tenant_id/project_id/track.section.task format; it exists only in the sync layer.

Implementation Plan

Phase 1: Foundation (Week 1-2)

TaskComponentDescription
1.1project-registry.jsonCreate initial registry with PILOT project
1.2task-id-validator.pyUpdate regex to accept optional project prefix
1.3resolve_project()Implement project context resolution function in scripts/core/task_id.py
1.4CODITECT_PROJECTDocument env var in CLAUDE.md and .env.example
1.5TestsUnit tests for new regex, resolution logic, registry validation

Phase 2: Integration (Week 3-4)

TaskComponentDescription
2.1task-tracking-enforcer.pyUpdate to pass project context to sync
2.2cloud_sync_client.pyAdd cloud_task_id generation with tenant prefix
2.3work_items.pyAccept unified format alongside E-T format
2.4work_item_bridge tableCreate bridge table in sessions.db
2.5/session-log commandAuto-prepend resolved project context
2.6.coditect-project fileAdd to PILOT repository root

Phase 3: Adoption (Week 5-8)

TaskComponentDescription
3.1Session logsAdd project prefix to new session log entries
3.2work_items.pyDefault new tasks to unified format
3.3work_items.pyShow unified equivalent for E-T tasks
3.4CLAUDE.mdUpdate Task ID Protocol section with unified format
3.5STANDARD-TRACK-NOMENCLATUREUpdate standard document
3.6/cxqSupport project-scoped queries (/cxq --project PILOT "search")

Phase 4: Deprecation (Week 9-12)

TaskComponentDescription
4.1work_items.pyEmit deprecation warning for E-T format input
4.2Migration scriptBulk-convert E-T references in sessions.db to unified format
4.3DocumentationRemove E-T examples from all documentation
4.4task-id-validator.pyAdd optional E-T rejection mode (configurable)

Migration Strategy

Existing Task ID Preservation

All existing task IDs in the following locations remain valid without modification:

LocationExampleStatus
TRACK files (TRACK-A-*.md)A.9.1.3Valid (resolves to PILOT:A.9.1.3)
Session logs[A.9.1.3]Valid (PILOT context assumed)
CLAUDE.md examplesA.9.1.3:Valid (backward compatible regex)
Governance hooksA.9.1.3: in descriptionsValid (extended regex accepts bare format)
sessions.db task_trackingE001-T001Valid (bridge table maps to unified)

Data Migration

sessions.db: No schema changes required for existing tables. The work_item_bridge table is additive. Existing task_tracking rows with E-T format IDs gain a bridge mapping but are not modified in place.

Session logs: Historical session logs are not retroactively modified. Only new entries gain explicit project context when working outside PILOT.

Cloud sync: Existing synced tasks are re-synced with cloud task IDs on next sync cycle. The cloud API accepts both old (bare track ID) and new (tenant-prefixed) formats during the transition period.

Rollback Plan

If the unified format causes issues:

  1. Set CODITECT_UNIFIED_TASK_ID=0 environment variable to disable project prefix parsing
  2. task-id-validator.py falls back to ADR-054 bare format validation
  3. work_items.py continues to accept E-T format without deprecation warnings
  4. No data migration is destructive; bridge table can be dropped without data loss

Examples

Developer Working on PILOT (Default)

# No changes to existing workflow
export CODITECT_PROJECT=PILOT # Optional, this is the default

# Tool call description - bare format still works
Bash(description="A.9.1.3: Run database migration")

# Explicit prefix also works
Bash(description="PILOT:A.9.1.3: Run database migration")

# Session log entry
### 2026-02-02T14:30:00Z - [A.9.1.3] Database migration

Developer Working on Customer Project

# Set project context
export CODITECT_PROJECT=CUST-001

# Tool calls automatically scoped to CUST-001
Bash(description="A.3.1.2: Set up customer database schema")
# Validates: track A exists in CUST-001's registered tracks

# Cross-project reference requires explicit prefix
Bash(description="PILOT:H.8.1.6: Check framework dependency")

# Session log entry includes project context
### 2026-02-02T15:00:00Z - [CUST-001:A.3.1.2] Customer database setup

Cloud Sync Scenario

# Local task
PILOT:A.9.1.3

# Synced to cloud as
az1ai/PILOT/A.9.1.3

# Customer task
CUST-001:A.3.1.2

# Synced to cloud as
tenant_acme_001/CUST-001/A.3.1.2

# Query from cloud API
GET /api/v1/context/tasks/?cloud_task_id=az1ai/PILOT/A.9.1.3
GET /api/v1/context/tasks/?project=CUST-001&track=A

Work Item Bridge

# Legacy command
python3 scripts/work_items.py show E001-T001
# Output:
# E001-T001 (PILOT:A.9.1.3)
# Status: completed
# Track: A (Backend API)
# Project: PILOT

# Unified command
python3 scripts/work_items.py show PILOT:A.9.1.3
# Output:
# PILOT:A.9.1.3 (legacy: E001-T001)
# Status: completed
# Track: A (Backend API)
# Project: PILOT
ADRRelationship
ADR-054Extended: Track nomenclature gains optional project prefix
ADR-056Extended: Action-level tracking gains project context
ADR-074Modified: Governance hooks updated for unified format
ADR-115Aligned: Hybrid task specification compatible with unified IDs
ADR-116Unchanged: Track-based plan architecture remains SSOT
ADR-117Extended: Hierarchical plan location gains project dimension
ADR-144Foundation: Project registry in org.db provides project metadata for task ID validation
ADR-145Extended: RBAC uses project prefix for project-scoped permission validation

References

  • ADR-054: Track Nomenclature Extensibility
  • ADR-056: Action-Level Task Tracking
  • ADR-074: Governance Hook Architecture
  • ADR-115: Hybrid Task Specification Standard
  • ADR-116: Track-Based Plan Architecture
  • ADR-117: Hierarchical Plan Location Strategy
  • ADR-144: Multi-Project Registry Architecture
  • ADR-145: Project-Level RBAC & Multi-Tenant Access Control
  • CODITECT-STANDARD-TRACK-NOMENCLATURE.md
  • CODITECT-STANDARD-AUTOMATION.md (Principle #12)
  • hooks/task-id-validator.py - Current PreToolUse validator
  • hooks/task-tracking-enforcer.py - Current TodoWrite enforcer
  • scripts/work_items.py - Current E-T format task management
  • scripts/core/cloud_sync_client.py - Cloud sync API client