***

title: NPC State And Protocol
description: 'Register NPCs, inspect slice state, and run the multi-turn protocol'
slug: ue/npc
------------

NPC state in the UE plugin lives in `NPCSlice`, not in a standalone immutable `FNPC` value. The slice mirrors the entity-adapter pattern from `@forbocai/core`.

## Primary Type

```cpp
USTRUCT(BlueprintType)
struct FNPCInternalState {
  UPROPERTY(BlueprintReadOnly) FString Id;
  UPROPERTY(BlueprintReadOnly) FString Persona;
  UPROPERTY(BlueprintReadOnly) FAgentState State;
  UPROPERTY(BlueprintReadOnly) TArray<FNPCHistoryEntry> History;
  FAgentAction LastAction;
  bool bHasLastAction;
  UPROPERTY(BlueprintReadOnly) bool bIsBlocked;
  UPROPERTY(BlueprintReadOnly) FString BlockReason;
  UPROPERTY(BlueprintReadOnly) TArray<FNPCStateLogEntry> StateLog;
};
```

The slice keeps an entity map plus `ActiveNpcId`.

## Register An NPC

```cpp
#include "RuntimeStore.h"
#include "NPC/NPCId.h"
#include "NPC/NPCSlice.h"

auto Store = createSDKStore();

FNPCInternalState Npc;
Npc.Id = NPCId::GenerateNPCId();
Npc.Persona = TEXT("A careful quartermaster.");
Npc.State = TypeFactory::AgentState(TEXT("{\"mood\":\"guarded\"}"));

Store.dispatch(NPCSlice::Actions::SetNPCInfo(Npc));
```

`SetNPCInfo` upserts the entity and also makes it active.

## Selectors

* `NPCSlice::SelectNPCById(State, Id)`
* `NPCSlice::SelectAllNPCs(State)`
* `NPCSlice::SelectActiveNpcId(State)`
* `NPCSlice::SelectActiveNPC(State)`

```cpp
const auto Active = NPCSlice::SelectActiveNPC(Store.getState().NPCs);
if (Active.hasValue) {
  UE_LOG(LogTemp, Display, TEXT("%s"), *Active.value.Persona);
}
```

## Update State

Use slice actions for direct state changes.

```cpp
Store.dispatch(NPCSlice::Actions::UpdateNPCState(
    Npc.Id,
    TypeFactory::AgentState(TEXT("{\"mood\":\"friendly\"}"))));
```

* `SetNPCState` replaces the full agent state
* `UpdateNPCState` merges a JSON delta into the current state
* `AddToHistory`, `SetHistory`, `SetLastAction`, `BlockAction`, and `ClearBlock` support the protocol runtime

## Run The Protocol

For synchronous tooling, use `SDKOps::ProcessNpc(...)`.

```cpp
#include "CLI/CliOperations.h"
#include "Memory/MemoryThunks.h"
#include "Cortex/CortexThunks.h"

SDKOps::InitNodeMemory(Store, TEXT("/tmp/quartermaster.db"));
SDKOps::InitCortex(Store, TEXT("smollm2-135m"));

FAgentResponse Response =
    SDKOps::ProcessNpc(Store, Npc.Id, TEXT("What supplies are left?"));
```

For direct parity with `packages/core`, dispatch `rtk::processNPC(...)` yourself from `Protocol/ProtocolThunks.h`.

## Protocol Semantics

* The API can return `IdentifyActor`, `QueryVector`, `ExecuteInference`, and `Finalize` instructions.
* `QueryVector` requires local memory support in the runtime.
* `ExecuteInference` requires local cortex support in the runtime.
* On successful finalize, the runtime persists memory instructions, merges state deltas, records the last action, and appends user/assistant history.
* On invalid finalize, the slice records `bIsBlocked` and `BlockReason`.

## Convenience Wrappers

`SDKOps` exposes the same NPC lifecycle in synchronous form:

* `SDKOps::CreateNpc(Store, Persona)`
* `SDKOps::GetActiveNpc(Store)`
* `SDKOps::UpdateNpc(Store, NpcId, Delta)`
* `SDKOps::ProcessNpc(Store, NpcId, Text)`
* `SDKOps::ListNpcs(Store)`
