# Numu — TD-001 → TD-005 Verification Package (gaps closed)

> **Truth-first.** This documents what is *actually implemented and enforced*. The three
> ownership gaps from the prior package (TD-002 investor parity, TD-003 pause enforcement,
> TD-005 expected-state validation) are now **closed and verified live**. Numu now fully owns
> execution, validation, policy enforcement, approvals, handoffs, and agent control.

**Deployment status (global):** all capabilities live in the codebase; the TD base (8 tables +
services + APIs) is committed (`1e977ef`, branch `main`); the orchestration layer, investor
parity, and these gap closures are **uncommitted in the working tree**. **Nothing is deployed to
production.** Every "implemented" = *implemented, not deployed*.

---

## 0. Architecture-alignment summary

Final architecture (Make = AI/orchestration only; **Numu = policy + approval + business
execution + validation + agent control + audit**) **is fully implemented.** Make does not execute
business actions, validate state, or enforce pauses — Numu does.

| TD | Capability | Status | Owner | Aligned? |
|---|---|---|---|---|
| TD-001 | Agent Runs | ✅ implemented | Numu BE + DB | ✅ |
| TD-002 | Handoff Storage | ✅ implemented (**startup + investor**) | Numu BE + DB | ✅ |
| TD-003 | Agent Pause/Enable | ✅ implemented (**enforced at execution**) | Numu BE + DB + dashboard | ✅ |
| TD-004 | Policy Configuration | ✅ implemented | Numu BE + DB + dashboard | ✅ |
| TD-005 | Expected-State Validation | ✅ implemented (**validated + enforced**) | Numu BE | ✅ |
| (post-TD) | Business Action Engine (decision→execute, approve→execute) | ✅ implemented | Numu BE | ✅ |

**Net:** Numu owns state, policy, **expected-state validation**, approvals, execution, handoffs,
**agent pause control (enforced)**, duplicate-prevention, concurrency, audit — for **both** startup
and investor subjects. Remaining items are non-architectural (seed investor agents; deploy).

---

## TD-001 — Agent Runs

**1. Status:** ✅ Fully implemented (not deployed). Startup ✅ + Investor ✅ (`agent_runs.investor_id`; exactly one of `startup_id|investor_id`).
**2. Location:** Numu backend (`AgentRunService`, `AgentRunController`, `AgentDecisionService`) + DB (`agent_runs`).
**3. Responsibility:** storage/source-of-truth/audit = Numu; duplicate-prevention = unique `idempotency_key` (dup→original); concurrency = create-race on the key + decision-field immutability when analysis terminal; retry = re-POST same key.
**4. API:** `POST /api/v1/ai/agent-runs` · `GET /agent-runs` · `GET /agent-runs/{run}` · `PATCH …/analysis-result` · `POST …/decision` · `POST …/execution-results` · `POST …/cancel`.
**5. Runtime:** dup key → 200 original; analysis immutable after terminal (409); invalid/missing subject → 422.

## TD-002 — Handoff Storage

**1. Status:** ✅ Fully implemented (not deployed). **Startup ✅ + Investor ✅** (`handoffs.investor_id` added; `startup_id` nullable; sequence is monotonic **per subject**).
**2. Location:** Numu backend (`HandoffService`, `HandoffController`) + DB (`handoffs`). Migration `…_add_investor_subject_to_handoffs`.
**3. Responsibility:** Numu = authoritative storage + audit + supersede chain (immutable history); Make authors handoff content.
**4. API:** `POST /handoffs` · `GET /handoffs/latest` · `GET /handoffs` · `GET /handoffs/{handoff}` · `POST /handoffs/{handoff}/supersede` — all accept exactly one of `startup_id|investor_id`.
**5. Runtime:** `latest('startup_id'|'investor_id', id, …)` returns the active row; supersede closes the prior + writes a new versioned row; audited. **Verified:** investor handoff stored with `investor_id`, `latest(investor)` returns it, per-investor sequence.

## TD-003 — Agent Pause / Enable (now enforced)

**1. Status:** ✅ Fully implemented (not deployed). Startup + Investor (per `agent_key`).
**2. Location:** Numu backend (`AgentRuntimeService` + **enforcement in `AgentDecisionService`**) + DB (`agent_runtime_config`) + dashboard + API.
**3. Responsibility:**
| Concern | Actual | Match |
|---|---|---|
| Pause/enable state + global `*` precedence | Numu | ✅ |
| Concurrency | Numu — version-checked (409), `lockForUpdate` | ✅ |
| **Enforcement at execution** | **Numu — `AgentDecisionService` blocks a paused OR disabled (`is_active=false`) agent BEFORE acting** | ✅ **closed** |

**4. API:** `GET /agents/runtime-status` · `GET /agents/{agentKey}/runtime-status` · `POST /agents/{agentKey}/pause|enable` · `POST /agents/pause-all|enable-all`.
**5. Runtime behavior (actual, verified):** a paused/disabled agent calling `/decision` →
**no policy resolution, no approval, no execution**; the run is recorded `execution_status=blocked`,
`error_code=agent_paused|agent_disabled`, and an audit row `agent.blocked_agent_paused` /
`agent.blocked_agent_disabled` is written. Re-enabling → executes normally. This holds **regardless
of Make** — Make cannot bypass it. (Human *approve* of a pre-existing pending item still executes —
pause governs autonomous agent action, not human overrides; documented intent.)

**Pause-mode scope (important):** the pause `mode` has three values — **`pause_new_only` (ENFORCED)**,
`cancel_queued` (future), `cancel_running_if_safe` (future). **Numu currently enforces only the
`pause_new_only` semantics: it blocks any *new* agent executions/decisions.** Numu does **not**
manage queue cancellation or interrupt an in-progress execution — there is no Numu-side run queue
or worker to cancel, because **Make is the orchestration layer** (runs arrive per-call from Make).
All three modes are stored, shown and audited, but at enforcement time `cancel_queued` and
`cancel_running_if_safe` currently behave like `pause_new_only`; they are **documented future
capabilities** (would require Make coordination to cancel queued/running work). The two future
modes are labelled "(future)" in the dashboard so operators aren't misled.

## TD-004 — Policy Configuration

**1. Status:** ✅ Fully implemented (not deployed). Startup ✅ + Investor ✅.
**2. Location:** Numu backend (`ActionPolicyService`, `PolicyOverrideService`) + DB (`agent_action_policies`, `policy_runtime_overrides`) + dashboard (Actions / Assigned Actions / Action Agent Policies).
**3. Responsibility:** Numu owns config + authorization + resolution (override → designed → **default blocked**) + versioned history + concurrency (409).
**4. API:** `GET /action-policies` · `GET /action-policies/{agent}` · `GET /action-policies/{agent}/{action}` (resolve) · `PUT /action-policies/{agent}/{action}` · overrides.
**5. Runtime:** unassigned pair → blocked (`source=default`); override wins; stale `expected_version` → 409. Enum `always_allow|needs_approval|blocked`.

## TD-005 — Expected-State Validation (now implemented + enforced)

**1. Status:** ✅ Fully implemented (not deployed). Startup + Investor (both expose `group()` + `statusOption()`).
**2. Location:** Numu backend — `ExpectedStateValidator` + enforcement in `AgentDecisionService` (always_allow path) **and** `ApprovalExecutionService` (approve path).
**3. Responsibility:** Numu validates the subject's **current** business state (group + status, plus optional `updated_at` staleness) against the state the agent based its decision on, before any Native Action runs. Only fields the agent provided are checked (null → skipped).

**Worked example (actual behavior now):**
```
startup current state = prescreen
run carries expected_status = "prescreen", action_slug = "move_to_committee"
  · if current status still "prescreen" → validation passes → action executes
  · if a human moved it (e.g. → "committee") → MISMATCH → execution REJECTED:
        run.execution_status = failed, error_code = expected_state_mismatch,
        execution_request = failed, audit = agent.expected_state_mismatch,
        response.decision = state_mismatch, executed = false, mismatches = [...]
```

**4. API:** validation is automatic on `/decision` (always_allow) and `/approvals/{id}/approve`.
Expectations are carried on the run (`expected_group`/`expected_status`, set by Make at run
creation) and on the approval (`current_group`/`current_status`, captured at creation → re-checked
at approve time).
**5. Runtime (verified):** match → executes; mismatch → rejected + recorded + audited; no
expectation provided → not blocked (optimistic). Enforced at **both** auto-execute and
human-approve execution.

---

## Investor Agent Support — explicit verdict (now full parity)

| Capability | Startup | Investor |
|---|---|---|
| Agent Runs | ✅ | ✅ (`investor_id`) |
| Policies | ✅ | ✅ |
| Approvals | ✅ | ✅ (`investor_id`) |
| Execution (single path) | ✅ `StartupLabelActionService` | ✅ `InvestorLabelActionService` |
| Expected-state validation | ✅ | ✅ |
| Pause enforcement | ✅ | ✅ |
| **Handoffs** | ✅ | ✅ (`investor_id`) |
| Audit / Notifications / Automations | ✅ | ✅ |
| Seeded default agents | ✅ (7) | — create-as-needed |

Investor support is now **architecturally complete** (only the seed list is empty by choice).

---

## Differences from historical TD documents

| # | Historical design | Actual implementation | Reason | Follow-up |
|---|---|---|---|---|
| 1 | Make executes the Native Action | **Numu executes** (orchestration → `LabelActionService`) | Final architecture | none |
| 2 | UUID PKs | bigint PKs | Phase-1 decision | none |
| 3 | Expected-state validated by Numu | **Implemented + enforced** at decision + approve | — | **done** |
| 4 | Runtime pause enforced by Numu | **Implemented + enforced** in the decision pipeline | — | **done** |
| 5 | Startup-scoped subject | **Runs/approvals/executions/handoffs carry `investor_id`** | Investor parity | none |
| 6 | Validation ownership = Make | Payload validation + transition-validity = **Numu** | — | none |

---

## Known limitations (post-closure)
- **Pause modes — only `pause_new_only` is enforced.** Numu blocks new executions/decisions for a
  paused agent. `cancel_queued` and `cancel_running_if_safe` are **documented future capabilities**
  (stored/shown/audited, labelled "(future)" in the UI, currently behaving as `pause_new_only`).
  Numu does not run a queue or interrupt in-flight work — **Make owns orchestration**, so cancelling
  queued/running runs would require Make coordination.
- **No seeded investor agents** — create via UI/API when the investor pipeline is standardized.
- **Not deployed to production** — orchestration + parity + gap closures are uncommitted; schedule
  `approvals:expire`; run a queue worker for downstream notifications; set `AI_AGENT_TESTING=false`.
- **Testing endpoints** (`/testing/*`) are off in production by config.
- **Make residual responsibilities:** none required for correctness anymore — Numu enforces pause
  and expected-state. (Make may still *pre-check* `runtime-status`/state to avoid wasted calls, but
  it is no longer load-bearing.)

---

## Runtime-behavior matrix (actual, all verified)

| Scenario | Behavior |
|---|---|
| duplicate request / idempotency | original returned; action not repeated (unique `idempotency_key` on runs + execution_requests) |
| expected-state **match** | executes |
| expected-state **mismatch** | **rejected** — run failed, exec-request failed, audit `agent.expected_state_mismatch`, `decision=state_mismatch` |
| unauthorized agent+action | **blocked** (`source=default`) |
| disabled agent (`is_active=false`) | **blocked** — `agent_disabled`, audited |
| paused agent | **blocked** — `agent_paused`, audited |
| blocked policy | not executed; run `execution_status=blocked` |
| invalid payload / missing subject | 422 |
| concurrent / duplicate decisions | idempotent (execution_request lock + dup→stored result) |
| duplicate approvals | one-shot (settled approval → 409) |
| failed native action | run/exec-request → `failed` + error |
| rollback | executed action = real business transition; **not auto-reversed** (reverse via inverse action) |
| audit logging | every step (`activity_logs`, module=`agents`) |
| notifications / automations | fire via the canonical `apply()` event listeners (unchanged) |

---

## Evidence (smallest useful set)
- **Postman:** `docs/postman/numu-ai-agents.postman_collection.json` (all endpoints + 7 scenarios + investor).
- **Docs:** this file · `AI_AGENTS_API_CONTRACT.md` · `AI_AGENT_ORCHESTRATION_REPORT.md` · `AI_AGENTS_INVESTOR_PARITY.md`.
- **Migrations:** `…110001…110010` (TD base) + `…_add_agent_type_to_agents` · `…_add_agent_decision_fields_to_approval_requests` · `…_add_context_to_agent_runs` · `…_add_investor_subject_to_agent_tables` · `…_add_investor_subject_to_handoffs`.
- **Services (owners):** `app/Services/Agents/` — incl. new `AgentDecisionService`, `AgentActionExecutor`, `ApprovalExecutionService`, **`ExpectedStateValidator`**.
- **Tests + live verification:** unit `tests/Feature/Agents/ManagedAgentsInvariantsTest.php` **14 pass**; live (auto-reverted, no prod data) — startup lifecycle 21/21, investor lifecycle 19/19, testing-endpoints 14/14, **gap closures 17/17** (expected-state match/mismatch, pause block/enable, investor handoff).
- **No secrets/tokens included.**

---

## Smallest Make integration (final)
Make: (1) generate recommendation; (2) `POST /agent-runs` (with `expected_group`/`expected_status`);
(3) `POST /agent-runs/{run}/decision`; (4) for `needs_approval`, rely on the Numu Approvals screen
(Numu executes on approve, after re-validating state). Numu now enforces policy, pause, expected-state,
approvals, execution, and audit — **Make duplicates none of it.**
