# Implementation Patterns: agentic-orchestrator

---

## Pattern 1: Returning Errors — Use `fail()` + `ERROR_CODES`

**Rule**: Never throw raw errors from service methods or tool handlers. Always return a structured `ToolResponse` using `fail()` with a code from `ERROR_CODES`.

### ❌ Anti-Pattern

```typescript
// WRONG: throws instead of returns; uses raw string instead of ERROR_CODES
throw new Error('lock not held');
```

### ✅ Canonical Example

**Source File**: `apps/control-plane/src/application/services/lock-service.ts`

```typescript
import { fail, ok } from '../../core/response.js';
import { ERROR_CODES } from '../../core/error-codes.js';

function releaseLock(featureId: string, sessionId: string): ToolResponse {
  if (!isOwner(featureId, sessionId)) {
    return fail(
      ERROR_CODES.LOCK_NOT_HELD,
      `Session ${sessionId} does not hold lock for ${featureId}`,
      { retryable: false, requires_human: false, featureId, sessionId },
    );
  }
  // ...release...
  return ok({ released: true });
}
```

### 🔗 References

- **Domain Logic**: `DOMAIN-LOGIC.md` rules 12–14
- **Error codes**: `apps/control-plane/src/core/error-codes.ts`
- **Response helpers**: `apps/control-plane/src/core/response.ts`

---

## Pattern 2: Port-Based Dependency Injection

**Rule**: Services expose an interface (port). The kernel constructs them as concrete implementations. Domain/application layers only depend on the interface, never the concrete class.

### ❌ Anti-Pattern

```typescript
// WRONG: domain class directly instantiates infrastructure
import { GitHubPRClient } from '../infra/github-client.js';
class MergeService {
  client = new GitHubPRClient(); // hard coupling
}
```

### ✅ Canonical Example

**Source File**: `apps/control-plane/src/application/services/merge-service.ts`

```typescript
// Define the port (interface)
export interface MergeServicePort {
  getRepoRoot(): string;
  getPolicySnapshot(): AnyRecord;
  // ...additional port methods
}

// Implement the service, depending on the port
export class MergeService {
  private readonly port: MergeServicePort;
  constructor(port: MergeServicePort) {
    this.port = port;
  }
  // ...
}

// Kernel wires it at construction time, passing the port implementation
const mergeService = new MergeService(port);
```

### 🔗 References

- **Architecture**: `ARCHITECTURE.md` — Component Map (Port-based DI row)
- **Domain Logic**: `DOMAIN-LOGIC.md` rule 15

---

## Pattern 3: Atomic State Reads and Writes

**Rule**: Always wrap file-based state mutations with `withFileLock()`. Never read-modify-write `state.md`, `plan.json`, or `index.json` without holding the lock.

### ❌ Anti-Pattern

```typescript
// WRONG: reads then writes without a lock — race condition in parallel runs
const state = await readJson(statePath);
state.status = 'building';
await atomicWriteJson(statePath, state);
```

### ✅ Canonical Example

**Source File**: `apps/control-plane/src/application/services/feature-state-service.ts`

```typescript
import { withFileLock, readJson, atomicWriteJson } from '../../core/fs.js';

async function transitionStatus(statePath: string, newStatus: string): Promise<ToolResponse> {
  return withFileLock(statePath, async () => {
    const state = await readJson(statePath);
    // validate transition before mutating
    const allowed = LEGAL_TRANSITIONS[state.status];
    if (!allowed?.includes(newStatus)) {
      return fail(
        ERROR_CODES.INVALID_STATUS_TRANSITION,
        `Cannot go from ${state.status} to ${newStatus}`,
      );
    }
    state.status = newStatus;
    await atomicWriteJson(statePath, state);
    return ok({ status: newStatus });
  });
}
```

### 🔗 References

- **Domain Logic**: `DOMAIN-LOGIC.md` rules 4–6
- **Utilities**: `apps/control-plane/src/core/fs.ts`

---

## Pattern 4: Adding a New CLI Command

**Rule**: Adding a new command requires updating **all five** of the following files in the same commit. Missing any one will cause lint or runtime failures.

### ✅ Checklist

1. **`apps/control-plane/src/cli/types.ts`** — add value to `CliCommand` enum
2. **`apps/control-plane/src/cli/cli-argument-parser.ts`** — parse the new token
3. **`apps/control-plane/src/interfaces/cli/bootstrap.ts`** — dispatch the new command
4. **`apps/control-plane/src/cli/help-command-handler.ts`** — add to `COMMAND_HELP` (summary + flags list)
5. **`CLAUDE.md` and `AGENTS.md`** — update command list in CLI bullet point

### 🔗 References

- **CLAUDE.md**: `CLAUDE.md` § Adding a New CLI Command or Flag
- **AGENTS.md**: `AGENTS.md` § Adding a New CLI Command or Flag

---

## Pattern 5: MCP Tool Idempotency

**Rule**: All mutating MCP tool calls must include `operation_id`. The operation ledger deduplicates retried calls automatically — callers must provide the ID, not check for duplicates themselves.

### ✅ Canonical Example

**Source File**: `apps/control-plane/src/mcp/operation-ledger.ts`

```typescript
// Caller provides a stable operation_id (e.g., derived from feature + patch sequence)
const result = await toolRuntime.executeTool({
  tool: 'repo.apply_patch',
  input: { featureId, patch },
  operation_id: `${featureId}:patch:${patchSeq}`,
  role: 'builder',
});
```

### 🔗 References

- **Domain Logic**: `DOMAIN-LOGIC.md` rule 9
- **Tool catalog**: `config/agentic/orchestrator/tools/catalog.json`

---

## Pattern 6: Structured TypeScript Imports

**Rule**: Use `import type` for any import used only as a type. ESLint `consistent-type-imports` is enforced — violations are errors.

### ❌ Anti-Pattern

```typescript
import { ToolResponse } from '../../core/response.js'; // used only as a type
```

### ✅ Canonical Example

```typescript
import type { ToolResponse } from '../../core/response.js';
import { ok, fail } from '../../core/response.js'; // runtime values
```

### 🔗 References

- **CLAUDE.md** § Lint Rules to Know

<!-- @cortex-tms-version 4.0.2 -->
