# Numu Managed Agents — Repository Question Answers

> **THE REPOSITORY IS THE SOURCE OF TRUTH.** Every answer below is derived only
> from the actual code, migrations, models, services, controllers, routes,
> configs, seeders, and tests in this repository. Where a question cannot be
> proven from the repository it is marked `NOT VERIFIED IN REPOSITORY`.
>
> This is a READ-ONLY verification document. No code was designed, implemented,
> or modified to produce it.

**Verification basis (git):**
- Branch `feat/agent-knowledge-cache`, HEAD `29f916a` ("Add Agent Runtime endpoint (propose-only) — Phase 5 Task #3").
- Prior committed work reachable from HEAD: `a9fa600` (registry-driven loader refactor + Handoff P1 hardening), `33d8e49` (Knowledge Cache hardening: per-environment pointers + content validation), `4b4854f` (Knowledge Cache: loader + immutable versions + runtime read).
- Origin remote: `https://dev.azure.com/Lun-Dev/numu/_git/numu` (branch mirrored). This clone's `.env` has `APP_URL=http://localhost` (a dev checkout).

---

# Repository ↔ Document Reconciliation

Mismatches found during verification between the design/request documents and
the actual code. **The repository is authoritative.** These are documented, not
"fixed" — no architecture or implementation was changed to produce this table.

| # | Area | Document states | Repository (source of truth) | Evidence |
|---|---|---|---|---|
| 1 | **Environment enum** | A single environment set | **Two distinct namespaces.** Knowledge/Loader/Runtime use `dev/staging/production` (default `production`); Decision/Policy/Agents use `dev/test/prod` (default `prod`). A runtime call uses `production`; the follow-on decision uses `prod`. | `config/agent_knowledge.php` (`environments`) vs `AgentRunController@decision` (`in:dev,test,prod`), `agents`/`agent_action_policies` env default `prod` |
| 2 | **Knowledge Cache tables** | Multi-table (`agent_knowledge_packages`, `_package_files`, `_active_versions`, `_refresh_runs`) | **Single table** `agent_knowledge_cache` with an ordered `files_json` list (per-file `relative_path/load_order/required/checksum/size_bytes/content`) + `package_version/registry_version/file_count/total_size_bytes` | `2026_07_02_130000_create_agent_knowledge_cache_table.php`; `App\Models\AgentKnowledgeCache` |
| 3 | **Knowledge package** | 4-file package (`SKILL.md`, `DECISION_RULES.md`, `NOTE_GUIDE.md`, `HANDOFF_SCHEMA.md`) in a `04 Knowledge` folder | **7-file registry package** under `04 Prompts` + shared `NUMU_CORE_SKILL.md`, defined by the registry JSON (no folder/file names hardcoded) | `config/agent_knowledge_registry.json`; `AgentKnowledgeRegistry` |
| 4 | **Runtime model default** | `claude-sonnet-5` (task) / `claude-opus-4-7` appears in service config | **Runtime** default is `claude-sonnet-5` (`config/agent_runtime.php`); the general Anthropic service default is `claude-opus-4-7` (`config/services.php`) — two separate configs, both real | `config/agent_runtime.php` (`default_model`), `config/services.php` (`services.anthropic.model`) |
| 5 | **Handoff idempotency / retention** | Backend idempotency (key or logical tuple) + retention behavior expected | **Neither exists.** Append-only; only server-assigned `sequence_number` is unique per subject; `retention_class` defaults `standard` and is **metadata-only** with no allow-list and no consumer; `schema_version` is a free string | `handoffs` migrations; `HandoffService::create()`; grep shows no `retention_class` consumer |
| 6 | **Anthropic validation status** | Runtime "calls Claude" | **Live Anthropic call is NOT exercised in the repo.** `AnthropicModelProvider` is implemented (reuses `services.anthropic`) but every test uses the deterministic `FakeModelProvider`; no live-invocation coverage | `AgentRuntimeRunTest` (binds `FakeModelProvider`); `AnthropicModelProvider` has no test |
| 7 | **`agent_runs.stage`** | Referenced as a column | **Does not exist.** The run's stage identity is `agent_key` | `2026_06_26_110003_create_agent_runs_table.php` |
| 8 | **`GET /knowledge` shape** | Returns full file bodies | Returns **metadata + a file manifest** (no bodies); bodies come from `/knowledge/runtime` or `/knowledge/{version}` | `AgentKnowledgeController@show` vs `@runtime`/`@showVersion` |
| 9 | **Branch `feat/agent-knowledge-cache`** | Assumed pre-existing with prior work | Prior work was committed on `main`; the branch was **created this phase** from `main` | git: branch head `29f916a`, prior commits `a9fa600`/`33d8e49`/`4b4854f` on `main` |
| 10 | **Handoff production trigger** | Runtime/backend or Make trigger on AgentRun completion | **No automated trigger** — no webhook/event/queue on completion; only a pollable `GET /agent-runs` and explicit `POST /handoffs` | `AgentRunService` (sets `completed_at`, emits no event) |

**Action:** none in this phase — discrepancies are recorded so downstream Phase-5
work reconciles docs *to* the repository, not the reverse.

---

# Part 1 — Required Research Areas

## A. Backend stack

| Item | Verified value | Evidence |
|---|---|---|
| Framework | Laravel `^12` (running 12.58.0) | `composer.json:11`; `app()->version()` = 12.58.0 |
| Language | PHP `^8.2` (running 8.2.12) | `composer.json:9`; `PHP_VERSION` |
| Database engine | MySQL | `config('database.default')` = `mysql` |
| ORM | Eloquent | models under `app/Models/*` |
| Migration system | Laravel migrations | `database/migrations/*` |
| Queue system | `database` driver | `config('queue.default')` = `database` |
| Cache system | `database` driver | `config('cache.default')` = `database` |
| Auth (API) | Sanctum `^4.3` + custom OAuth 2.1 + `McpAuth` middleware | `composer.json:12`; `app/Http/Middleware/McpAuth.php` |
| Permissions | Spatie `laravel-permission ^6.25`, guard `admin` | `composer.json:16`; `AgentPermissionsSeeder` |
| AI feature flag | `config('ai.enabled')` = true (gates all `/api/v1/ai/*`) | `routes/api.php:12` |
| Deployment | `NOT VERIFIED IN REPOSITORY` | No CI/deploy config, no server path, no proof this tree deploys to `dashboard.numuangels.net`. The repository IS the Laravel backend (has `artisan`, `composer.json`, `app/`, `routes/`, `config/`, `database/migrations/`), but the deployment target is not provable from the repo. |

**Confidence:** VERIFIED (stack); NOT VERIFIED (deployment).

---

## B. Knowledge Cache

| Aspect | Verified implementation | Evidence |
|---|---|---|
| Table | Single table `agent_knowledge_cache` | `2026_07_02_130000_create_agent_knowledge_cache_table.php` (+ `2026_07_02_140000_add_environment_to_agent_knowledge_cache.php`, `2026_07_02_150000_augment_knowledge_traceability.php`) |
| Model | `App\Models\AgentKnowledgeCache` | constants `STATUS_ACTIVE/SUPERSEDED/FAILED`, `SOURCE_LOCAL/GRAPH` |
| Active pointer | Exactly one `is_active = true` per `(agent_key, environment)`; `scopeActive` | `AgentKnowledgeCache::scopeActive`; `AgentKnowledgeReader::active()` |
| Rollback | Pointer-only via `AgentKnowledgeLoader::activateVersion()` (flips `is_active`/`status` under `lockForUpdate`) | `AgentKnowledgeLoader.php` |
| Immutability | `booted()` `updating` guard allows only `is_active`/`status`/`updated_at`; `deleting` throws | `AgentKnowledgeCache::booted()` (`MUTABLE_AFTER_CREATE`) |
| Environment | `environment` column; independent sequence + pointer per `(agent_key, environment)` | reader/loader are env-scoped |
| Versioning | Monotonic `'v'.(maxVersionNumber+1)` per env; `unique(agent_key, environment, version)` | `AgentKnowledgeLoader::refresh()`; `AgentKnowledgeReader::maxVersionNumber()` |
| Validation | `KnowledgeValidator` (size, UTF-8, null-byte/binary, executable `MZ/ELF/#!/<?php/<?=`, `<script>`/markup) | `app/Services/Agents/Knowledge/KnowledgeValidator.php` |
| Audit | `AgentAuditLogger` → `activity_logs`; events `agent.knowledge_loaded` / `_unchanged` / `_failed` / `_validation_failed` / `_rollback` | `AgentKnowledgeLoader` constants `EV_*` |
| Stored files | Ordered list `files_json` = `[{relative_path, load_order, required, checksum, size_bytes, content}]` + `file_count`, `total_size_bytes`, `package_version`, `registry_version`, bundle `checksum` | `AgentKnowledgeLoader::refresh()` |

**Confidence:** VERIFIED.

---

## C. Loader

| Aspect | Verified implementation | Evidence |
|---|---|---|
| Entry points | `AgentKnowledgeLoader::refresh($agentKey,$environment,$actorId)`, `refreshAll()`, `activateVersion()` | `app/Services/Agents/Knowledge/AgentKnowledgeLoader.php` |
| Registry | `AgentKnowledgeRegistry` reads `config/agent_knowledge_registry.json` via `config('agent_knowledge.registry_path')` | `AgentKnowledgeRegistry::load()` |
| Registry validation | Rejects empty path, duplicate `relative_path`, duplicate `load_order`, wildcard (`*`/`?`), absolute path, `..` traversal; sorts by `load_order` | `AgentKnowledgeRegistry::validatedFiles()` |
| Source read | `KnowledgeSource::fetch(array $relativePaths)` — exact registered paths only; `LocalFolderSource` + `GraphSharePointSource`, config-selected | `app/Contracts/Knowledge/KnowledgeSource.php`; `AppServiceProvider` binding on `config('agent_knowledge.source')` |
| Required-file rule | Missing required → `KnowledgeSourceException`; empty required (trim=='') → `KnowledgeSourceException`; keeps previous active | `AgentKnowledgeLoader::refresh()` |
| Checksum | Per-file `sha256`; bundle `sha256` over ordered `load_order\0rel\0content\0` | `AgentKnowledgeLoader::refresh()` |
| No-op refresh | If active checksum == new checksum → `EV_UNCHANGED`, no new version | `AgentKnowledgeLoader::refresh()` |
| Rollback | Pointer-only `activateVersion()`; never edits content | same |
| Permissions | `ai.knowledge.refresh`, `ai.knowledge.rollback` | `AgentPermissionsSeeder.php:22-23` |
| Environment | `config('agent_knowledge.environments')` = `['dev','staging','production']`, default `production` | `config/agent_knowledge.php` |

**Confidence:** VERIFIED.

---

## D. Runtime

| Aspect | Verified implementation | Evidence |
|---|---|---|
| Exists? | YES | `POST /api/v1/ai/agent-runtime/run` → `AgentRuntimeRunController::run()` (`routes/api.php`) |
| Production-ready? | PARTIAL — implemented + unit/feature-tested with the deterministic `FakeModelProvider`; the **live Anthropic call is not exercised anywhere in the repo tests** | `AgentRuntimeRunTest` binds `FakeModelProvider`; `AnthropicModelProvider` has no test |
| Placement | `app/Services/Agents/Runtime/*` (+ `Provider/*`); controller `app/Http/Controllers/Api/V1/Ai/Agents/AgentRuntimeRunController.php` | — |
| Endpoint contract | Request `{agent_key, startup_id, agent_run_id, environment?}` | `AgentRuntimeRunController::run()` `$request->validate` |
| Model provider | Interface `RuntimeModelProvider`; `AnthropicModelProvider` (reuses `config('services.anthropic.*')`), `FakeModelProvider`; bound by `config('agent_runtime.provider')` | `AppServiceProvider` binding |
| Prompt builder | `RuntimePromptBuilder` — metadata header + role frame + active package's ordered `files_json` + strict output contract | `RuntimePromptBuilder::build()` |
| Structured validation | `StructuredOutputValidator` — required fields, `confidence∈[0,1]`, allowed action per agent, no-execution-claim denylist, size cap, whitelist output | `StructuredOutputValidator::validate()` |
| Schema repair | Exactly one — a **second full provider call** with a repair instruction, then re-validate; else fail closed | `RuntimeRunner::invokeAndValidate()` |
| Fail-closed | Preconditions before model call; invalid-after-repair → `StructuredOutputException` → 422 | `RuntimeRunner`, `AgentRuntimeRunController` |
| Traceability | 12 metadata fields stamped on the run | `RuntimeRunner::stampTraceability()` |
| Context retrieval | `RuntimeContextBuilder` reuses `Startup` relations + `HandoffService::latest` + recent runs/approvals | `RuntimeContextBuilder::build()` |
| Token accounting | Summed across all calls (initial + repair) via `usage()`; latency summed; `runtime_calls` exposed | `RuntimeRunner::usage()` |
| Write behavior | The ONLY write in `app/Services/Agents/Runtime/*` is `RuntimeRunner.php` `$run->fill([...])->save()` (traceability) | grep of the namespace returns one write |
| Execution boundaries | Propose-only — never decide/approve/execute; submission stays on `POST /agent-runs/{run}/decision` | `RuntimeRunner` has no `AgentDecisionService`/pipeline call |
| Default model | `claude-sonnet-5`; per-agent override; `allowed_actions.intake_triage = [move_to_review, reject, stop]` | `config/agent_runtime.php` |

**Confidence:** VERIFIED (implementation + fake-model tests); NOT VERIFIED (live Anthropic invocation).

---

## E. AgentRun

**Schema sources:** `2026_06_26_110003_create_agent_runs_table.php` (base), investor-parity migration, `2026_07_02_150000_augment_knowledge_traceability.php`, `2026_07_04_120000_add_runtime_metrics_to_agent_runs.php`. Model `App\Models\AgentRun`.

| Group | Fields (verified) |
|---|---|
| Traceability columns | `knowledge_version`, `knowledge_checksum`, `knowledge_package_id`, `knowledge_registry_version`, `knowledge_loaded_at`, `source_stale`, `runtime_model`, `prompt_contract_version`, `runtime_provider`, `runtime_latency_ms`, `runtime_token_input`, `runtime_token_output` |
| Decision fields (`DECISION_FIELDS`) | `decision_payload`, `proposed_action`, `proposed_status_update`, `note_payload`, `expected_group`, `expected_status`, `expected_record_version` |
| Execution fields (`EXECUTION_FIELDS`) | `execution_status`, `execution_result`, `activity_log_id`, `notification_result` |
| Immutability | `DECISION_FIELDS` freeze once analysis is terminal, enforced by **`AgentRunService`** via `AgentRun::isAnalysisLocked()` (statuses `completed/failed/cancelled`). The `AgentRun` model itself has NO `updating` guard | `AgentRun::isAnalysisLocked()`, `AgentRunService` |
| Runtime-added fields | The 4 metrics (`runtime_provider/latency_ms/token_input/token_output`) + reuse of the 8 traceability columns; stamped by `RuntimeRunner`, which touches **only** metadata columns | `2026_07_04_120000`; `RuntimeRunner::stampTraceability()` |
| `stage` column | **Does NOT exist** on `agent_runs` (only `core_skill_version`/`stage_skill_version`). The run's stage identity is its `agent_key` | migration `2026_06_26_110003` |

**Confidence:** VERIFIED.

---

## F. Handoff

| Aspect | Verified implementation | Evidence |
|---|---|---|
| Routes | `POST /handoffs`, `GET /handoffs/latest`, `GET /handoffs`, `GET /handoffs/{id}`, `POST /handoffs/{id}/supersede` (all `ai` prefix, `McpAuth:full`) | `routes/api.php` |
| Schema | `handoffs` (`2026_06_26_110004` + `2026_06_29_130000_add_investor_subject` + `2026_07_02_160000_add_investor_sequence_unique`) | — |
| Columns | `startup_id`/`investor_id`, `source_run_id`, `from_agent`, `to_agent`, `stage`, `schema_version`, `sequence_number`, `current_group/status`, `proposed_action`, `resulting_group/status`, `facts/findings/risks/open_questions/documents_used/decision_summary` (json), `superseded_by`, `retention_class`, `created_at` (no `updated_at`) | migrations + `Handoff` model |
| Latest retrieval | `HandoffService::latest()` = `whereNull(superseded_by)` + optional `from_agent`/`schema_version` + `orderByDesc(sequence_number)->first()` | `HandoffService.php` |
| Supersede | Implemented; immutable original + `superseded_by` link; already-superseded → 409 (locked re-check) | `HandoffService::supersede()`; `HandoffHardeningTest` |
| Correction | Supersede IS the correction mechanism (new immutable row, old links via `superseded_by`) | same |
| Semantic validation (P1) | `source_run_id` must belong to subject, run completed, `agent_key == from_agent`, `stage == run.agent_key` | `HandoffController::validateSourceRun()`; `HandoffHardeningTest` |
| Duplicate prevention | **Backend does NOT dedup by logical tuple.** Only `unique(startup_id, sequence_number)` + `unique(investor_id, sequence_number)` (server-assigned sequence, never collides) | migrations; `HandoffService::create()` |
| Idempotency | `NOT VERIFIED IN REPOSITORY` (no idempotency key / logical-tuple uniqueness on `(entity, source_run_id, from_agent, to_agent, schema_version)`); caller pre-check required | — |
| Retention | `retention_class` string(40) default `'standard'`; **any** ≤40-char string; **metadata-only** (no code reads it for retention) | migration; grep shows no consumer |
| Status values | **No status column.** Lifecycle = `superseded_by` null (active/latest) vs not-null (superseded) | `Handoff` model |
| Schema versions | `schema_version` free string ≤40, **no allow-list** | `HandoffController::store` rules |
| Payload limits | No app-level field/array/string caps; DB errors mapped by `AgentApiController::guard()` → 409 (unique) / 413 (packet/data too large) / 422 (other) | `AgentApiController::mapQueryException()` |

**Confidence:** VERIFIED (implemented behavior); idempotency = NOT VERIFIED (absent by design).

---

## G. Decision Pipeline

**Verified chain** (`App\Services\Agents\AgentDecisionService::decide()`):

```
POST /agent-runs/{run}/decision
 → AgentDecisionService::decide()
   → AgentRunService::recordAnalysisResult()      (freeze analysis, idempotent)
   → pauseBlock()  [AgentRuntimeService::effectiveStatus]   (TD-003)
   → ActionPolicyService::resolve()               (override → designed → default blocked)
   → branch on policy:
       ALWAYS_ALLOW   → ExecutionRequestService::claim()
                        → ExpectedStateValidator::validate()   (TD-005)
                        → AgentActionExecutor::executeForRun()
                        → ExecutionRequestService::recordResult()
       NEEDS_APPROVAL → ApprovalService::createFromRun()   (PENDING; no execution)
       blocked        → recordExecutionResult(blocked)
```

| Component | Exists | Immutable? | Runtime may call? |
|---|---|---|---|
| `AgentDecisionService` | ✅ | — | ❌ never (entry is the decision endpoint, invoked by Make) |
| `ActionPolicyService` | ✅ | — | ❌ never |
| `ApprovalService` / `ApprovalExecutionService` | ✅ | approvals one-shot (`STATUS_TERMINAL`) | ❌ never |
| `ExpectedStateValidator` | ✅ | — | ❌ never |
| `AgentActionExecutor` | ✅ | — | ❌ never |
| `ExecutionRequestService` | ✅ | idempotency-keyed | ❌ never |
| `HandoffService` | ✅ | handoffs immutable (append-only) | Runtime **reads** latest via `HandoffService::latest` only; never creates/supersedes |

**What Runtime may call:** knowledge read (`AgentKnowledgeReader`), context read (`Startup`/`HandoffService::latest`/`AgentRun`/`AgentApprovalRequest` queries), model provider, validator, and one traceability write to its own `AgentRun`. **What Runtime may never call:** any of the seven pipeline services above to mutate state.

**Confidence:** VERIFIED (chain, from `AgentDecisionService.php`).

---

## H. API Routes (verified from `routes/api.php` / `routes/web.php`)

All `/api/v1/ai/*` Managed-Agents routes are behind middleware **`McpAuth:full`, `ai.cb`, `throttle:600,1`**, prefix `ai`. Success envelope `{data, meta:{request_id, correlation_id, actor}}`; error `{error:{code, message, details}}` (`AgentApiController`).

| Area | Method + path | Handler |
|---|---|---|
| Knowledge (metadata + manifest) | `GET ai/agents/{agentKey}/knowledge` | `AgentKnowledgeController@show` |
| Knowledge (runtime content) | `GET ai/agents/{agentKey}/knowledge/runtime` | `@runtime` |
| Knowledge (history) | `GET ai/agents/{agentKey}/knowledge/versions` | `@versions` |
| Knowledge (meta) | `GET ai/agents/{agentKey}/knowledge/meta` | `@meta` |
| Knowledge (exact version) | `GET ai/agents/{agentKey}/knowledge/{version}` | `@showVersion` |
| **Runtime** | `POST ai/agent-runtime/run` | `AgentRuntimeRunController@run` |
| AgentRuns | `POST ai/agent-runs`, `GET ai/agent-runs`, `GET ai/agent-runs/{run}`, `PATCH ai/agent-runs/{run}/analysis-result`, `POST ai/agent-runs/{run}/execution-results`, `POST ai/agent-runs/{run}/cancel` | `AgentRunController` |
| **Decision** | `POST ai/agent-runs/{run}/decision` | `AgentRunController@decision` |
| Approvals | `GET ai/approvals`, `GET ai/approvals/{approval}`, `POST ai/approvals/{approval}/{decide,approve,reject}` | `ApprovalApiController` |
| Handoffs | `POST ai/handoffs`, `GET ai/handoffs/latest`, `GET ai/handoffs`, `GET ai/handoffs/{handoff}`, `POST ai/handoffs/{handoff}/supersede` | `HandoffController` |
| Knowledge refresh (admin) | `POST admin/agents/{agent}/refresh-knowledge` — `permission:ai.knowledge.refresh,admin` | `AgentConsoleController@refreshKnowledge` (`routes/web.php`) |
| Knowledge rollback (admin) | `POST admin/agents/{agent}/knowledge/{environment}/{version}/activate` — `permission:ai.knowledge.rollback,admin` | `AgentConsoleController@rollbackKnowledge` (`routes/web.php`) |

**Runtime request validation:** `agent_key` (required string ≤80), `startup_id` (required int), `agent_run_id` (required int), `environment` (nullable string ≤40). **Runtime errors:** `404 not_found` · `422 precondition_failed` · `409 knowledge_unavailable` · `422 invalid_model_output` · `502 provider_error`.

**Confidence:** VERIFIED.

---

## I. Enums / Constants (verified from model constants + validation rules)

| Enum | Verified values | Source |
|---|---|---|
| Agent runtime status | `enabled`, `paused` | `AgentRuntimeConfig::STATUS_ENABLED/PAUSED` |
| AgentRun analysis status | `queued`, `running`, `completed`, `failed`, `cancelled` | `AgentRun::ANALYSIS_*` |
| AgentRun execution status | `not_started`, `executing`, `executed`, `blocked`, `cancelled`, `failed` | `AgentRun::EXEC_*` |
| Approval status | `pending`, `approved`, `rejected`, `cancelled`, `expired` | `AgentApprovalRequest::STATUS_*` |
| Approval risk level | `none`, `expected`, `high` | `AgentApprovalRequest::RISK_*` |
| Execution request status | `processing`, `succeeded`, `failed`, `cancelled`, `duplicate`, `deferred` | `ExecutionRequest::STATUS_*` |
| Action policy | `always_allow`, `needs_approval`, `blocked` | `AgentActionPolicy::*` |
| Handoff status | **No status enum** — `superseded_by` null/not-null only | `Handoff` model |
| Retention class | Default `standard`; any string ≤40 (no enum) | `handoffs` migration |
| Runtime provider | `anthropic`, `fake` | `config/agent_runtime.php` + `AppServiceProvider` |
| Runtime default model | `claude-sonnet-5` | `config/agent_runtime.php` |
| Action slugs (intake_triage) | `move_to_review`, `reject`, `stop` | `config('agent_runtime.allowed_actions.intake_triage')` |
| Action slugs (general) | Free string; validated generically by decision endpoint | `AgentRunController@decision` (`action_slug` required string ≤120) |
| Environment (knowledge/loader/runtime) | `dev`, `staging`, `production` (default `production`) | `config/agent_knowledge.php` |
| Environment (agents/policies/decision) | `dev`, `test`, `prod` (default `prod`) | `AgentRunController@decision` (`in:dev,test,prod`); `agents`/`agent_action_policies` env default `prod` |
| Startup groups / statuses | `NOT VERIFIED IN REPOSITORY` as fixed enums — they are DB rows (`Group`, `LabelOption`), not code constants | `Startup::group()`, `Startup::statusOption()` |

> **Notable discrepancy (repo is source of truth):** the Knowledge/Loader/Runtime environment namespace (`dev/staging/production`) is **different** from the decision/policy/agent environment namespace (`dev/test/prod`). A runtime call uses `production`; the subsequent decision call uses `prod`. These are two distinct enums in the code.

**Confidence:** VERIFIED (code constants); startup group/status enums = NOT VERIFIED.

---

## J. MCP vs REST matrix (verified from `McpToolRegistry` + `routes/api.php`)

The MCP connector (`app/Services/Ai/Mcp/McpToolRegistry.php`) exposes **no** Managed-Agents tools — its tools are `investor_*`, `startup_*`, `committee_*`, `demo_*`, `meeting_*`, `note_*`, `tag_*`, `label_option_*`, `activity_log_*`, `notification_*`, `task_*`, `startup_member_*`, `startup_file_*`. All Managed-Agents operations are REST-only behind `McpAuth:full`.

| Area | MCP | REST | Writes | Status | Recommended |
|---|---|---|---|---|---|
| AgentRun create/read/list | ❌ | ✅ | ✅ | committed | REST |
| Decision submit | ❌ | ✅ | ✅ | committed | REST |
| Approvals read/decide | ❌ | ✅ | ✅ | committed | REST |
| Handoff create/read/latest/supersede | ❌ | ✅ | ✅ | committed | REST |
| Knowledge read/runtime/versions/meta | ❌ | ✅ | read | committed | REST |
| Knowledge refresh/activate/rollback | ❌ | ✅ (admin web route) | ✅ | committed | Admin web (permission-gated) |
| Agent Runtime run | ❌ | ✅ | proposal + traceability only | committed | REST |
| Startup / investor lookup | ✅ | ✅ | read | committed | MCP for read; REST for writes |

**Confidence:** VERIFIED.

---

## K. Security

| Aspect | Verified implementation | Evidence |
|---|---|---|
| Secret storage | Environment variables read via `config()`; no secrets committed in the repo | `config/services.php`, `config/microsoft_bookings.php` |
| Anthropic API key | `config('services.anthropic.api_key')` = `env('ANTHROPIC_API_KEY')`; never returned by any API; provider errors sanitized to `HTTP <code>` (no body/key) | `AnthropicModelProvider::complete()` |
| Graph credentials | `GraphTokenProvider` via `MS_GRAPH_*` (`config/microsoft_bookings.graph.*`) | `GraphSharePointSource` reuses it |
| API auth | `McpAuth` (OAuth 2.1 `naat_` bearer with `numu:write` + audience binding, OR Sanctum `numu:full`); tokens-in-query rejected; tokens never logged | `app/Http/Middleware/McpAuth.php` |
| Audit logging | `AgentAuditLogger` → `activity_logs` (module `agents`, source `api`, actor stamped) for decision, knowledge (load/rollback), handoff (created/superseded), runtime (`agent.runtime_run`) | `AgentAuditLogger`, service call sites |
| PII minimization | `RuntimeContextBuilder::startup()` uses an explicit allowlist; excludes `email`, `phone_number`, `applicant_full_name`, `first_name_*`, `linkedin_url` | `RuntimeContextBuilder.php` |
| Prompt logging | Not persisted — `RuntimeRunner` stores no prompt text; only token counts/model/latency in traceability columns | `RuntimeRunner::stampTraceability()` |
| Response logging | Not persisted — the model response text is validated then discarded; only the sanitized proposal is returned (not stored on the run) | `RuntimeRunner::run()` |
| Provider data-retention / no-training config | `NOT VERIFIED IN REPOSITORY` — no such setting exists in code/config | — |
| Runtime restrictions | Propose-only, single traceability write, fail-closed, no-execution-claim denylist | `RuntimeRunner`, `StructuredOutputValidator` |

**Confidence:** VERIFIED (implemented controls); NOT VERIFIED (provider retention/no-training config; secret rotation/ownership = operational, not in repo).

---

# Part 2 — Developer Questions (Unified Developer Request)

## Question 6

### Question
Verify the real production backend repository (path/Git URL, framework, current branch incl. whether `feat/agent-knowledge-cache` exists, setup, test command, existing AI Agents/Handoff/Knowledge/Runtime code locations, existing migrations incl. `2026_07_02_150000_augment_knowledge_traceability`, risks).

### Repository Evidence
- `composer.json`, `artisan`, `app/`, `routes/`, `config/`, `database/migrations/`; git remote `origin = https://dev.azure.com/Lun-Dev/numu/_git/numu`; branch `feat/agent-knowledge-cache` @ `29f916a`.
- `database/migrations/2026_07_02_150000_augment_knowledge_traceability.php` — present.
- Code locations: `app/Services/Agents/Knowledge/*`, `app/Services/Agents/Runtime/*`, `app/Models/{AgentRun,AgentKnowledgeCache,Handoff}.php`, `app/Http/Controllers/Api/V1/Ai/Agents/*`, `app/Services/Agents/{AgentDecisionService,ApprovalService,ActionPolicyService,ExpectedStateValidator,AgentActionExecutor,HandoffService,ExecutionRequestService,AgentRunService}.php`.

### Verified Answer
This IS the Laravel backend. Framework Laravel 12 / PHP 8.2. Branch `feat/agent-knowledge-cache` **now exists** (created this phase from `main`; it did not exist before). Test command: `php artisan test` (agents suite: `php artisan test tests/Feature/Agents`). The traceability migration exists. All required code is present.

### Confidence
VERIFIED (code/branch/migration). NOT VERIFIED: that this exact tree is the deployment powering `dashboard.numuangels.net` (no deploy config in repo).

### Notes
`.env` here is a dev checkout (`APP_URL=http://localhost`).

---

## Question 7.1 — Runtime activation checklist for approval metadata

### Question
Confirm whether any runtime activation checklist verifies Training Pack / Core Skill approval metadata before loading the runtime package.

### Repository Evidence
`AgentKnowledgeRegistry` (per-file `required`/`load_order` only), `AgentKnowledgeLoader::refresh()`, `KnowledgeValidator`.

### Verified Answer
`NOT VERIFIED IN REPOSITORY`. The loader validates file presence/order/content safety and checksums; there is **no** check of any "Approved v1.0" approval-metadata flag before activation. Approval status is not a field the code reads.

### Confidence
NOT VERIFIED (feature absent).

---

## Question 7.2 — Official API routes + enums/limits

### Question
Confirm official production routes and authoritative enums/limits/validation for Handoff, AgentRun, decision, approval, knowledge, runtime.

### Repository Evidence
`routes/api.php`; §H and §I tables above.

### Verified Answer
Routes: see §H (the `/api/v1/ai` prefix is real and behind `McpAuth:full`). Enums: see §I. Payload limits for handoffs/knowledge are **not** enforced at field level in code (only knowledge `max_file_size`, default 1 MB; DB errors mapped in `guard()`).

### Confidence
VERIFIED (routes + code enums). PARTIALLY VERIFIED (limits — several are environment/DB-level, not app-enforced).

### Notes
Two environment namespaces exist (see §I discrepancy).

---

## Question 7.3 — `pre_screen` vs `prescreen`

### Question
Does the backend enforce/seed `prescreen`? Is aliasing/migration needed? Where is the canonical value enforced?

### Repository Evidence
Grep of seeders/config; `config/agent_knowledge_registry.json` (only `intake_triage` enabled); no routing table keyed on the second-stage slug.

### Verified Answer
```
Official key:        NOT VERIFIED IN REPOSITORY (no seeded second-stage agent_key found; only intake_triage is registry-enabled)
Compatibility notes: Handoff from_agent/to_agent are free strings (≤80); no enum enforces prescreen vs pre_screen
Migration needed:    NOT VERIFIED (no code path depends on the value)
Alias behavior:      none in code
Where to enforce:    nowhere currently — from_agent/to_agent/stage are unconstrained strings
```

### Confidence
NOT VERIFIED (no enforcement point exists in the repo).

### Notes
Because `from_agent`/`to_agent`/`stage` are free strings, either value is accepted today; the P1 handoff validation only checks `from_agent == source_run.agent_key` and `stage == source_run.agent_key`.

---

## Question 7.4 — Handoff supersede / correction contract

### Question
Provide the supersede endpoint + contract, or formally defer.

### Repository Evidence
`routes/api.php` (`POST ai/handoffs/{handoff}/supersede`), `HandoffController::supersede()`, `HandoffService::supersede()`, `HandoffHardeningTest`.

### Verified Answer
**Implemented (Option A).** `POST /api/v1/ai/handoffs/{id}/supersede` (`McpAuth:full`). Creates a NEW immutable handoff inheriting `startup_id/investor_id/source_run_id/from_agent/to_agent/stage/schema_version/retention_class`; sets old `superseded_by = new.id`; new row `superseded_by = null`; `GET latest` returns the new row. Re-superseding an already-superseded handoff → **409 conflict** (locked re-check). Original content never mutated (model immutability guard).

### Confidence
VERIFIED.

### Notes
No idempotency on supersede itself (each call creates a new row unless the target is already superseded).

---

## Question 7.5 — Handoff backend idempotency

### Question
Idempotency key / unique tuple / append-only + caller dedup?

### Repository Evidence
`handoffs` migrations (`unique(startup_id, sequence_number)`, `unique(investor_id, sequence_number)`); `HandoffService::create()`.

### Verified Answer
**Option B (append-only) is what exists.** There is **no** idempotency key and **no** logical-tuple uniqueness. The only uniqueness is on server-assigned `sequence_number` per subject (never collides). A duplicate logical POST creates a new row. Caller-side pre-`GET latest` dedup is required.

### Confidence
VERIFIED (idempotency absent).

---

## Question 7.6 — Production Handoff trigger implementation

### Question
Runtime-owned / Make fallback / Hybrid; trigger conditions; flow.

### Repository Evidence
No scheduler/job/event creates handoffs; `HandoffController` is the only creation path; `AgentRunService` sets `completed_at` but emits no event.

### Verified Answer
`NOT VERIFIED IN REPOSITORY` — no automated production Handoff trigger exists (no webhook/event/queue on AgentRun completion). Handoffs are created only by an explicit authenticated `POST /handoffs`. Whoever calls it (Make or Runtime) must perform the pre-POST duplicate check.

### Confidence
NOT VERIFIED (trigger not implemented).

---

## Question 7.7 — Handoff id 3 impact + cleanup

### Question
Does the duplicate affect `GET latest` / downstream; safest cleanup path.

### Repository Evidence
`HandoffService::latest()` orders by `sequence_number DESC` among non-superseded.

### Verified Answer
Per code: `GET latest` returns the **highest non-superseded `sequence_number`**, so a later duplicate (higher sequence) **would** be returned instead of the earlier row — matching the reported observation. Cleanup path available in code: `POST /handoffs/{original}/supersede` (or point the duplicate's `superseded_by`), which removes it from `latest`. The specific row "id 3 for startup 551" is production data — `NOT VERIFIED IN REPOSITORY` (no fixtures/seeders for it).

### Confidence
PARTIALLY VERIFIED (behavior verified; the specific record is not in the repo).

---

## Question 7.8 — PII / Anthropic implementation

### Question
PII minimization, prompt/response logging, provider retention config, audit metadata store.

### Repository Evidence
`RuntimeContextBuilder` allowlist; `RuntimeRunner` (no prompt/response persistence); `AnthropicModelProvider` (no key logging); traceability columns.

### Verified Answer
PII minimization = explicit allowlist (excludes email/phone/name/social). Prompt/response = **not logged/stored** by the runtime. Provider data-retention/no-training config = `NOT VERIFIED IN REPOSITORY`. Audit metadata stored = the 12 traceability columns + `activity_logs` `agent.runtime_run` event.

### Confidence
VERIFIED (minimization + no logging); NOT VERIFIED (provider retention setting).

---

## Question 7.9 — Environment availability

### Question
Which environments exist; staging; env-specific knowledge; env representation.

### Repository Evidence
`config/agent_knowledge.php` (`dev/staging/production`); decision `in:dev,test,prod`.

### Verified Answer
Config declares knowledge environments `dev/staging/production`; Knowledge Cache rows are env-scoped (`environment` column, per-env pointer). Whether a **staging deployment** physically exists is `NOT VERIFIED IN REPOSITORY`. Env is represented as a request param (`?environment=` / `environment` body field) and a DB column.

### Confidence
PARTIALLY VERIFIED (env modeling verified; deployment existence not).

---

## Question 7.10 — Secret storage & rotation

### Question
Where secrets live; rotation; which stay in Make; access.

### Repository Evidence
`config/services.php` (anthropic), `config/microsoft_bookings.php` (graph), `.env` (not committed).

### Verified Answer
Backend secrets are env-vars read via `config()` (`ANTHROPIC_API_KEY`, `MS_GRAPH_*`). Rotation/ownership/Make-specific secrets = `NOT VERIFIED IN REPOSITORY` (operational; no rotation code or secret-manager integration in repo).

### Confidence
PARTIALLY VERIFIED (storage mechanism); NOT VERIFIED (rotation/ownership).

---

## Question 7.11 — Rollback / incident process

### Question
Rollback mechanisms, who executes, alert destinations, incident log.

### Repository Evidence
`AgentKnowledgeLoader::activateVersion()` (knowledge rollback); admin routes; `activity_logs`.

### Verified Answer
Knowledge Cache rollback = `activateVersion()` (pointer-only) via `POST admin/agents/{agent}/knowledge/{environment}/{version}/activate` (perm `ai.knowledge.rollback`). Runtime/deploy rollback + alert destinations + incident-log location = `NOT VERIFIED IN REPOSITORY`.

### Confidence
PARTIALLY VERIFIED (knowledge rollback); NOT VERIFIED (deploy/alerts/incident log).

---

## Question 7.12 — Write endpoint verification policy

### Question
Safe endpoints, test records, pre/post/audit checks, forbidden writes.

### Repository Evidence
Route middleware (`McpAuth:full`), `AgentApiController` envelope, `activity_logs`.

### Verified Answer
`NOT VERIFIED IN REPOSITORY` as a formal policy — the repo enforces auth/scope/permission on write routes and audits writes, but there is no encoded "safe test record" policy or approval-gate-for-tests artifact.

### Confidence
NOT VERIFIED (policy is operational, not in code).

---

## Question 7.13 — MCP vs REST coverage matrix

### Question
Provide the matrix.

### Repository Evidence
`McpToolRegistry` tool names; `routes/api.php`.

### Verified Answer
See §J. Managed-Agents operations (runs/decisions/approvals/handoffs/knowledge/runtime) are **REST-only**; MCP covers investor/startup/committee/demo/meeting/notes/tags/logs/tasks/files reads+writes.

### Confidence
VERIFIED.

---

## Question 9 — Required error responses (verified subset)

### Question
Deterministic error responses for the listed cases.

### Repository Evidence
`AgentApiController::{guard, fail, mapQueryException}`, `HandoffController`, `AgentRuntimeRunController`, `McpAuth`.

### Verified Answer
| Case | Verified behavior |
|---|---|
| Invalid token | `401` (`McpAuth::reject`) |
| Missing scope | `403 insufficient_scope` (`McpAuth`) |
| Invalid `source_run_id` (handoff) | `422` framework validation (`exists` + semantic) |
| Run not belonging / not completed / `from_agent` mismatch (handoff) | `422` (`HandoffController::validateSourceRun`) |
| Duplicate Handoff | **No dedup** — new row created (idempotency absent) |
| Payload too large | `413 payload_too_large` (mapped `QueryException`) |
| Supersede already superseded | `409 conflict` |
| Supersede target not found | `404` (route-model binding) |
| No active knowledge (runtime) | `409 knowledge_unavailable` (fail-closed before model) |
| AgentRun/startup mismatch (runtime) | `422 precondition_failed` |
| Invalid model output (runtime) | `422 invalid_model_output` (after one repair) |
| Provider timeout/error (runtime) | `502 provider_error` |
| Invalid `schema_version` / `retention_class` | **Not rejected** — free strings (no allow-list) |

### Confidence
VERIFIED (implemented); the "invalid schema_version/retention_class → reject" and "duplicate handoff → 409" requirements are **not** met in code.

---

## Question 13 — Production trigger event

### Question
Can Numu provide webhook/event/queue/pollable endpoint for AgentRun completion with the specified payload?

### Repository Evidence
`AgentRunService` (sets `completed_at`, no event); `GET ai/agent-runs`, `GET ai/agent-runs/{run}`.

### Verified Answer
Only a **pollable endpoint** exists (`GET /api/v1/ai/agent-runs` / `/{run}`). No outbound webhook, event stream, or consumable queue on run completion exists in the repo. The specified push payload = `NOT VERIFIED IN REPOSITORY` (not implemented).

### Confidence
VERIFIED (poll-only; push not implemented).

---

# Architecture Verified

✓ Knowledge Cache — `agent_knowledge_cache` table, immutable versions, per-env pointer, rollback, audit (VERIFIED)
✓ Loader — registry-driven `AgentKnowledgeLoader` + `AgentKnowledgeRegistry` + sources + validator (VERIFIED)
✓ Runtime — `POST /api/v1/ai/agent-runtime/run`, propose-only, provider abstraction, validation+repair, traceability (VERIFIED for implementation + fake-model tests)
✓ AgentRun — schema + traceability/decision/execution field groups + runtime metrics (VERIFIED)
✓ Handoff — routes, immutable supersede, semantic validation, investor sequence unique (VERIFIED)
✓ Decision Pipeline — `AgentDecisionService` chain (VERIFIED)
✓ Approval Pipeline — `ApprovalService`/`ApprovalExecutionService` one-shot approvals (VERIFIED)
✓ Execution Pipeline — `ExecutionRequestService` + `AgentActionExecutor` + `ExpectedStateValidator` (VERIFIED)

# Still Missing (not implemented in the repository)

- Automated production Handoff trigger (no webhook/event/queue on AgentRun completion).
- Backend Handoff idempotency / logical-tuple uniqueness.
- `schema_version` and `retention_class` allow-lists (both accept any string).
- Runtime activation check of Training-Pack / Core-Skill approval metadata.
- Live Anthropic invocation coverage (tests use `FakeModelProvider` only).
- Provider data-retention / no-training configuration.
- Deploy/runtime rollback mechanism, alert destinations, incident-log location.
- A seeded/enforced second-stage `agent_key` (`pre_screen` vs `prescreen` unconstrained).
- Formal write-endpoint test policy artifact.
- Notes / uploaded-documents runtime context (deferred; full history for handoffs — currently latest-only).

# Design Documents That Are Now Stale

- Any reference to a **4-file** knowledge package or a **`04 Knowledge`** folder — the repo uses the registry-driven **7-file `04 Prompts`** package (`config/agent_knowledge_registry.json`).
- Any doc stating multiple knowledge cache tables (`agent_knowledge_packages` / `_package_files` / `_active_versions` / `_refresh_runs`) — the repo uses a **single** `agent_knowledge_cache` table with an ordered `files_json` list.
- Any doc asserting `agent_runs.stage` exists — it does not; the run's stage identity is `agent_key`.
- Any doc listing the runtime default model as `claude-opus-4-7` (that is `config/services.anthropic.model`); the **runtime** default is `claude-sonnet-5` (`config/agent_runtime.php`).
- Any doc that treats the branch `feat/agent-knowledge-cache` as pre-existing with prior work — prior work was on `main`; the branch was created this phase.
- Any doc implying `GET /knowledge` returns full file bodies — it returns metadata + a manifest; bodies come from `/knowledge/runtime` or `/knowledge/{version}`.

# Open Questions That Cannot Be Answered From The Repository

- Whether this repository is the exact tree deployed to `dashboard.numuangels.net` (no deploy config).
- Whether a staging environment physically exists.
- Anthropic provider data-retention / no-training account settings.
- Secret rotation cadence and per-secret access ownership.
- Alert destinations, on-call/incident owner, and incident-log location.
- The live production Handoff records (e.g. "id 3 for startup 551") — production data, not in repo.
- Which secrets, if any, remain in Make.
- The authoritative startup group/status value lists (DB-driven `Group`/`LabelOption` rows, not code enums).
