Fundamentals

PHP Data Placement

Install the PHP 8.2+ SDK and enable the cURL extension for the control-plane helper:

composer require neutron-ai/sdk

The official SDK handles runtime memory operations. The helper below calls the public Nucleus REST endpoints for provisioning and readiness checks.

Provision the three Nuclei

<?php

declare(strict_types=1);

function requiredEnv(string $name): string
{
    $value = getenv($name);
    if ($value === false || trim($value) === '') {
        throw new RuntimeException("{$name} is required");
    }
    return $value;
}

function neutronRequest(
    string $token,
    string $method,
    string $path,
    ?array $body = null,
): array {
    $base_url = rtrim(requiredEnv('NEUTRON_API_URL'), '/');
    $curl = curl_init($base_url . $path);
    if ($curl === false) {
        throw new RuntimeException('Unable to initialize cURL');
    }

    curl_setopt_array($curl, [
        CURLOPT_CUSTOMREQUEST => $method,
        CURLOPT_HTTPHEADER => [
            'authorization: Bearer ' . $token,
            'content-type: application/json',
        ],
        CURLOPT_POSTFIELDS => $body === null ? null : json_encode($body, JSON_THROW_ON_ERROR),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 30,
    ]);

    $raw_body = curl_exec($curl);
    $status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
    $error = curl_error($curl);
    curl_close($curl);

    if ($raw_body === false) {
        throw new RuntimeException($error ?: 'Neutron request failed');
    }
    $decoded = $raw_body === '' ? [] : json_decode($raw_body, true, flags: JSON_THROW_ON_ERROR);
    if ($status >= 400) {
        throw new RuntimeException("Neutron request failed with HTTP {$status}");
    }
    return is_array($decoded) ? $decoded : [];
}

$profiles = [
    [
        'nucleusId' => 'product-knowledge-global',
        'name' => 'Global product knowledge',
        'placement' => [
            'mode' => 'global',
            'guarantee' => 'none',
            'notes' => ['Approved global knowledge; no jurisdictional storage guarantee.'],
        ],
    ],
    [
        'nucleusId' => 'support-operations-weur',
        'name' => 'Western Europe support operations',
        'placement' => [
            'mode' => 'regional',
            'regionHint' => 'weur',
            'r2LocationHint' => 'weur',
            'guarantee' => 'best_effort_regional',
            'notes' => ['Latency preference only; not a legal residency boundary.'],
        ],
    ],
    [
        'nucleusId' => 'regulated-cases-eu',
        'name' => 'EU regulated case memory',
        'placement' => [
            'mode' => 'jurisdictional',
            'jurisdiction' => 'eu',
            'regionalServicesRequired' => true,
            'metadataBoundaryRequired' => true,
            'geoKeyManagerRequired' => false,
            'guarantee' => 'jurisdictional_storage',
            'notes' => [
                'Requires the EU Durable Object jurisdiction and matching EU archive binding.',
                'Processing, logs, metadata, keys, retention, and contracts require separate verification.',
            ],
        ],
    ],
];

$workspace_key = requiredEnv('NEUTRON_WORKSPACE_API_KEY');
foreach ($profiles as $profile) {
    neutronRequest($workspace_key, 'POST', '/v1/nuclei', $profile);
}

Treat an already-existing Nucleus as deployment state. Never retry creation with a different placement profile.

Build the memory router

<?php

use NeutronAI\NeutronAIClient;

$base_url = requiredEnv('NEUTRON_API_URL');
$global_memory = new NeutronAIClient(
    base_url: $base_url,
    token: requiredEnv('NEUTRON_GLOBAL_TOKEN'),
    nucleus_id: 'product-knowledge-global',
);
$western_europe_memory = new NeutronAIClient(
    base_url: $base_url,
    token: requiredEnv('NEUTRON_WEUR_TOKEN'),
    nucleus_id: 'support-operations-weur',
);
$eu_regulated_memory = new NeutronAIClient(
    base_url: $base_url,
    token: requiredEnv('NEUTRON_EU_TOKEN'),
    nucleus_id: 'regulated-cases-eu',
);

function storeGlobalProductKnowledge(NeutronAIClient $client, string $text): array
{
    return $client->remember([
        'scopeId' => 'kb:approved-products',
        'type' => 'knowledge',
        'privacyClass' => 'public',
        'text' => $text,
    ]);
}

function storeWesternEuropeSupportLesson(NeutronAIClient $client, string $text): array
{
    return $client->remember([
        'scopeId' => 'history:support-resolutions',
        'type' => 'tool_lesson',
        'privacyClass' => 'tenant',
        'text' => $text,
    ]);
}

function storeEuRegulatedCase(
    NeutronAIClient $client,
    string $case_id,
    string $minimized_summary,
): array {
    return $client->remember([
        'scopeId' => "case:{$case_id}",
        'type' => 'experience',
        'privacyClass' => 'user_private',
        'text' => $minimized_summary,
        'metadata' => [
            'dataClass' => 'eu-regulated-case',
            'source' => 'approved-case-summary',
        ],
    ]);
}

Expose only these domain-specific operations to application code. Do not accept placement or unrestricted scope values from an agent, browser, or request body.

Recall EU case context

$eu_case_context = $eu_regulated_memory->agentContext([
    'scopeIds' => [
        'case:case-4821',
        'policy:eu-case-handling',
        'kb:approved-products',
    ],
    'agentId' => 'agent:eu-case-support',
    'task' => 'Prepare the next authorized case-support step.',
    'privacyClasses' => ['tenant', 'user_private'],
    'tokenBudget' => 1_200,
    'cachePolicy' => [
        'mode' => 'prefer_cache',
        'keyMode' => 'intent_profile',
        'ttlSeconds' => 120,
        'includeDynamicRag' => true,
        'allowSensitive' => false,
    ],
]);

All requested scopes resolve inside regulated-cases-eu; the call cannot cross into another Nucleus.

Verify jurisdictional readiness

$eu_token = requiredEnv('NEUTRON_EU_TOKEN');
$placement = neutronRequest(
    $eu_token,
    'GET',
    '/v1/nuclei/regulated-cases-eu/placement',
);
$health = neutronRequest(
    $eu_token,
    'GET',
    '/v1/nuclei/regulated-cases-eu/health',
);

$blocking_warnings = [
    'jurisdictional_do_subnamespace_unavailable',
    'jurisdictional_archive_bucket_missing',
];
$active_warnings = $health['warnings'] ?? [];
if (array_intersect($blocking_warnings, $active_warnings) !== []) {
    throw new RuntimeException('EU Nucleus is not ready');
}

Return to the placement architecture and production checklist.