The small persistent PHP runtime behind a Composer-native application ecosystem.
Rust owns the runtime. Tokio owns concurrency. PHP owns your product.
Read the docs · Install PAM · Create your first app · Explore PAM Native
PAM — PHP, Always in Memory — is an unapologetically ambitious answer to a simple question: how far can PHP go when we stop rebuilding and discarding it on every request?
Much further.
PAM keeps the Zend Engine and your Composer application alive inside a supervised, event-driven runtime powered by Rust, Tokio, and PHP Fibers. The runtime owns execution, processes, transports, and its small request/response contract. HTTP routing, Laravel, Native, Desktop, Octane, authentication, and future capabilities belong to installable Composer packages.
This is not PHP imitating another ecosystem. This is PHP with a modern systems boundary built around its strengths.
Important
PAM 2.0 establishes the permanent ecosystem boundary: PAM is the runtime; Composer packages own product features and add their commands dynamically. Read the project status and known limitations, then validate your own extensions, devices, credentials, and workloads before production deployment.
| Build | What PAM changes | What you keep |
|---|---|---|
| PAM runtime | Persistent Zend, transports, async I/O and supervised processes | PHP, Composer, PSRs, your application |
pushinbr/pam-http |
Express-like routing and application structure | Install with pam composer require |
pushinbr/pam-laravel |
Laravel integration and package-owned Artisan command | Laravel conventions and packages |
pushinbr/pam-native |
Native SDK, platform CLI and dynamic PAM commands | Android/iOS features stay outside the runtime |
pushinbr/pam-desktop |
Desktop host, lifecycle and dynamic PAM commands | Desktop features stay outside the runtime |
Most platforms ask you to choose between the PHP ecosystem and a modern runtime. PAM rejects the trade-off. Composer stays Composer. Laravel stays Laravel. Rust handles transport, scheduling, reconciliation, supervision, and the native edge; PHP remains the expressive product layer your team already knows.
No global PHP. No global Composer. No FPM pool. No Rust toolchain for application developers. Install one verified runtime and create the product you want:
$ curl --proto '=https' --proto-redir '=https' --tlsv1.2 \
--connect-timeout 15 --max-time 60 --max-filesize 1048576 -fsSL \
https://github.com/push-in/pam/releases/latest/download/install.sh | sh
$ mkdir my-api && cd my-api
$ pam composer require pushinbr/pam-http
✓ Composer dependencies installed
$ pam dev
⚡ PAM listening on http://127.0.0.1:3000
↻ Hot reload enabled
$ curl http://127.0.0.1:3000/api/ping
{"data":{"status":1,"message":"pong","requestId":null}}Your application is now warm and persistent. Composer loaded once. The runtime
stays alive. Requests execute in isolated Fibers. The generated API is already
split into a named controller action, application service, readonly snapshot,
JSON Resource and integer-backed readiness enum; index.php validates typed
configuration and enables security headers before listening. The same starter
also contains an executable Eloquent product flow with ProductController
method mapping, Form Request, typed DTO, service, repository, migration,
integer-backed status enum, Resource collection, and 201 Created response.
Schema changes remain explicit through pam composer migrate, avoiding
multi-worker migration races during application startup.
Choose another target without learning another platform CLI:
pam init my-laravel-app --template laravel # Persistent Laravel
pam init my-native-app --template native # Android + iOS
pam init my-desktop-app --template desktop # Linux + macOS + Windows
pam init my-product --template product # Server + Native + DesktopThe product preset creates one coordinated workspace—not three unrelated
examples. It places the PAM API in apps/server, the Android/iOS application
in apps/native, the Desktop application in apps/desktop, and a versioned
integer-backed PHP contract plus shared light/dark design tokens in
packages/contracts. The generated vertical slice includes a bounded offline
check-in queue, authenticated Desktop worker commands, cross-surface status,
tests, cleanup rules, and a deterministic release manifest. Use --no-install
to generate it without downloading dependencies.
Inside every PAM project, the workflow is the same:
pam doctor
pam support --output pam-support.json
pam support --manager --output pam-support-manager.json
pam dev
pam test
pam build
pam packageWhen a development or CI environment needs investigation, pam support emits a
bounded JSON report to standard output without copying source files, environment
variables, or network data. Paths are redacted, persistence is explicit, and an
existing report is never overwritten. See the CLI guide.
Add --manager to include a bounded, separately hashed and path-redacted process
health/resource snapshot; log contents remain excluded.
PAM Native does not ship a DOM, a browser pretending to be an app, or a JavaScript runtime between your state and the platform. PHP describes the UI; Rust validates, diffs, and lays it out; Kotlin and Swift commit bounded mutations to real native controls on the UI thread.
Write a reactive component in PHP:
<?php
use Pam\Native\Attributes\State;
use Pam\Native\Component;
final class Counter extends Component
{
#[State]
public int $count = 0;
public function increment(): void
{
$this->count++;
}
}
?>
<template>
<Column class="counter">
<Text class="eyebrow">LIVE NATIVE STATE</Text>
<Text class="value">{{ $count }}</Text>
<Button label="Increment" @press="increment" />
</Column>
</template>
<style scoped>
.counter { padding: 24px; gap: 16px; align-items: center; }
.eyebrow { font-size: 12px; font-weight: 700; }
.value { font-size: 56px; font-weight: 800; }
</style>Tap the native button. PAM dispatches the event to the persistent PHP component,
updates #[State], renders the next typed tree, calculates the minimal diff in
Rust, and commits only the necessary mutation to Android or iOS.
native event → PHP action → reactive state → Rust diff → UI-thread commit
The result is a product-grade native foundation:
- real Android Views and UIKit controls;
- native stacks, tabs, drawers, sheets, headers, gestures, and transitions;
- recycled lists and grids built for large datasets;
- navigation, scroll, and focused-input preservation during hot reload where supported;
- native animations and gestures without per-frame traffic through PHP;
- camera, media, files, SQLite, secure storage, notifications, location, sensors, background work, sharing, widgets, App Intents, and Live Activities;
- a plugin SDK for product-specific Kotlin and Swift modules or views;
- bounded queues, payloads, caches, restoration, diagnostics, profiling, and repeatable performance budgets.
Start it with the same CLI:
$ pam init orbit --template native
$ cd orbit
$ pam doctor --fix
$ pam dev
✓ PHP component runtime started
✓ Native host connected
⚡ Hot reload readyGo deeper: Native overview · components · state and lifecycle · navigation · hot reload · plugin SDK
PAM Desktop combines typed PHP application logic, Rust process supervision, and local HTML/CSS/JavaScript rendered by Servo inside native windows. It supports multiple windows, bidirectional commands and events, background jobs, hot reload, crash recovery, signed updates, rollback, and native distribution.
Power is explicit. Grant only what the application needs:
$app->capabilities(
Capabilities::none()
->filesystem(FileSystemRoot::readWrite('workspace', __DIR__.'/storage'))
->dialogs()
->clipboard()
->notifications()
->dragAndDrop(),
);
$app->command('greet', static fn (CommandContext $command): CommandResult =>
CommandResult::success([
'message' => 'Hello, '.$command->string('name', 'world').'.',
]),
);Browser code can invoke only registered commands. Filesystem access is limited to named roots. The local bridge uses a random loopback port, a cryptographically random per-process token, matching-origin checks, and a restrictive CSP.
$ pam init studio --template desktop
$ cd studio
$ pam desktop doctor
$ pam desktop dev
$ pam desktop build
✓ Application bundle created
✓ SHA-256 integrity manifest writtenBuild self-contained packages for Linux, macOS, or Windows; create DEB, DMG, or MSIX output on the corresponding native host; and keep feed signing plus updates behind explicit PHP policy and a pinned Ed25519 public key.
Note
PAM Desktop 1.2 is alpha software and pins a Servo LTS release. It is ready for prototypes and controlled applications, but does not claim Electron feature parity yet. Release evidence records the exact engine revision instead of presenting that moving implementation detail as the product version.
Explore Desktop: overview · windows and commands · native capabilities · security · distribution
$ pam init my-laravel-app --template laravel
$ cd my-laravel-app
$ pam dev pam.php
⚡ Laravel booted once and listening on http://127.0.0.1:3000PAM downloads the official skeleton, installs normal Composer dependencies,
runs package discovery, generates the application key, and keeps Laravel intact
in vendor. Each request receives an isolated application sandbox while the
framework remains warm.
Artisan remains Artisan:
pam artisan migrate
pam artisan route:list
pam artisan test
pam artisan queue:workThe executable compatibility matrix covers Laravel 12 and 13 with SQLite, MySQL, PostgreSQL, Redis, database queues, Artisan, Sanctum, Scout, Livewire, Inertia, Reverb, Telescope, and Pulse. See the Laravel documentation for the lifecycle, package matrix, observability, Cloud, Forge, autoscaling, and deployment contracts.
Existing Laravel Octane applications can keep Octane's lifecycle and use PAM's Rust/Tokio transport through the optional bridge:
pam composer require laravel/octane pushinbr/pam-octane
pam octane:startPAM Octane supports PHP 8.4 with Laravel 12 or 13 and Octane 2.19+. Start with the package guide, then review the production and security contracts before deployment.
Traditional PHP PAM
request process starts
├─ bootstrap PHP ├─ start Zend + Tokio
├─ load Composer ├─ load Composer
├─ build application ├─ build application once
├─ execute handler request 1 ─┐
└─ discard everything request 2 ─┼─ isolated Fibers
request request N ─┘
└─ repeat all of the above process stays alive
PAM is not a new language, a PHP fork, a framework, or a Composer replacement. It is the systems layer beneath your application:
- persistent Zend Engine through the official Embed SAPI;
- Tokio scheduling, native async I/O, streaming, and backpressure;
- HTTP/1.1, HTTP/2, HTTP/3, and RFC 6455 WebSockets on the same runtime;
- supervised master/worker processes, crash recovery, graceful drain, worker recycling, and generational reload;
- Prometheus metrics, health endpoints, structured logs, tracing, profiling,
diagnostics, and live
pam top; - verified TLS, timeouts, request limits, slowloris protection, CORS, rate limiting, and trusted-proxy controls;
- relocatable bundles containing the application,
vendor, runtime, exactlibphp, and a SHA-256 manifest.
The PAM Supremacy delivery contract defines the evidence required before PAM 2.0 can be called mature or stable.
| I want to… | Start here |
|---|---|
| Understand the whole platform | Introduction |
| Install PAM | Installation |
| Build my first application | Create your first app |
| Choose Server, Laravel, Native, or Desktop | Choose a target |
| Learn every command | CLI and project console · repository CLI reference |
| Understand the internals | Architecture · How it works |
| Operate PAM in production | Production · official production guide |
| Run Laravel Octane | PAM Octane · release checklist |
| Inspect maturity and support | Project status · Known limitations |
The sections below are the deep technical reference: packages, APIs, Composer, PSRs, async I/O, WebSockets, production, performance, architecture, and validation.
The split is intentional. Pam's binary plays the role of Node's runtime and standard library. It owns process lifecycle, the event loop, network transports, native I/O, memory boundaries, diagnostics and the PHP Embed ABI. It does not own application routing or package installation.
pam binary
├── PHP Embed + Fibers + Tokio
├── HTTP/TLS/QUIC + low-level Request/Response server
├── async I/O, streams, files, DNS, processes and signals
└── lifecycle, workers, health, metrics and diagnostics
Composer
├── pushinbr/pam-http routing + middleware (the Express-like layer)
├── pushinbr/pam-socket realtime events (the Socket.IO-like layer)
├── pushinbr/pam-http-psr standards interoperability
├── pushinbr/pam-http-testing in-memory HTTP application tests
├── pushinbr/pam-octane Laravel Octane bridge
├── pushinbr/pam-desktop desktop application model (separate repository)
└── every existing compatible PHP package
The core can serve HTTP without a framework:
use Pam\Http\Request;
use Pam\Http\Response;
use Pam\Http\Server;
Server::create(static fn (Request $request, Response $response): Response =>
$response->json(['path' => $request->path])
)->listen(3000);Install only the higher-level pieces your application needs:
pam composer require pushinbr/pam-http
pam composer require pushinbr/pam-socket # optional
pam composer require pushinbr/pam-http-psr # optional
pam composer require --dev pushinbr/pam-http-testing # optionalSee Packages and extension model for stability, discovery and publishing rules.
pushinbr/pam-http is the optional, Express-like API:
<?php
declare(strict_types=1);
use Pam\App;
use Pam\Http\Request;
use Pam\Http\Response;
$app = new App();
$app->get('/api/users', static function (Request $request, Response $response): Response {
return $response->json([
'search' => $request->getQuery('search'),
'requestId' => $_SERVER['PAM_REQUEST_ID'],
]);
});
$app->post('/api/messages', static function (Request $request, Response $response): Response {
return $response->json([
'accepted' => true,
'message' => $request->json(),
], 201);
});
$app->listen(3000);Run it:
pam dev index.php
curl 'http://127.0.0.1:3000/api/users?search=ada'Pam also supports the PHP behavior existing applications expect:
$_GET,$_POST,$_COOKIE,$_FILES,$_REQUEST, and$_SERVER;php://input;- JSON, URL-encoded forms, multipart bodies, and uploads;
- repeated response headers;
header(),setcookie(), andhttp_response_code();- native PHP sessions.
Superglobals, output buffers, headers, sessions, uploads, Fiber context, and request-scoped resources are restored or cleaned after every request.
Composer is PAM's only package registry and dependency resolver. PAM does not maintain a package allowlist, a second package format, or a parallel registry.
pam composer require guzzlehttp/guzzle
pam composer install
pam doctor
pam testPam discovers the nearest composer.json, respects config.vendor-dir, and
loads the normal Composer autoloader. composer.lock is the dependency source
of truth. Package integrity, provenance, security advisories and repository
metadata use Composer and Packagist's standard mechanisms.
Pam packages use the same mechanism. A third-party package can publish a service provider under extra.pam.providers; pushinbr/pam-http discovers it from Composer's installed metadata and writes an atomic cache under .pam/cache. Set PAM_DISABLE_PACKAGE_DISCOVERY=1 for fully explicit registration.
The executable compatibility project currently covers real behavior from:
- Amp 3 and Revolt;
- ReactPHP;
- Guzzle 8;
- Monolog;
- OpenTelemetry;
- Illuminate Container, Events, and Pipeline 13;
- Symfony HttpFoundation and HttpKernel 8;
- Slim 4;
- PHPUnit and Pest;
- PSR-3, PSR-7, PSR-15, and PSR-17.
Pure PHP packages generally work through normal autoloading. Extensions work when they are loaded by the Embed SAPI and compatible with a persistent process. Packages that assume FPM, Apache, CLI-only behavior, or request-local global state must be validated.
Read the full Composer compatibility contract.
Install the official PSR interfaces through Composer and use existing middleware contracts directly:
<?php
declare(strict_types=1);
use Pam\App;
use Pam\Http\Psr7\Factory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
$factory = new Factory();
$app = new App();
$app->handler(new class($factory) implements RequestHandlerInterface {
public function __construct(private readonly Factory $factory)
{
}
public function handle(ServerRequestInterface $request): ResponseInterface
{
return $this->factory
->createResponse(200)
->withHeader('content-type', 'application/json')
->withBody($this->factory->createStream(json_encode([
'method' => $request->getMethod(),
'path' => $request->getUri()->getPath(),
], JSON_THROW_ON_ERROR)));
}
});
$app->listen(3000);Each HTTP request runs in its own root Fiber. When native work must wait, the Fiber yields a typed operation to Rust. Tokio waits for the timer, descriptor, DNS lookup, filesystem task, process, or signal while other requests continue making progress.
use Pam\Filesystem\File;
use Pam\Http\Client;
use Pam\Net\Dns;
use Pam\Process\Command;
use function Pam\Async\all;
use function Pam\Async\async;
use function Pam\Async\delay;
$results = all([
'profile' => async(static function (): string {
delay(0.025);
return File::read(__DIR__ . '/profile.json', maxBytes: 1_048_576);
}),
'addresses' => async(static fn (): array => Dns::resolve('example.com', timeout: 2.0)),
'health' => async(static fn () => (new Client(timeout: 3.0))
->get('https://example.com/health')
->json()),
]);
$command = Command::run(
[PHP_BINARY, '-r', 'echo hash("sha256", "pam");'],
timeout: 2.0,
);The async layer includes:
- Futures and
await; - timers and deadlines;
- cancellation tokens;
- bounded channels;
- Fiber-local context;
- mutexes;
- pollable stream readiness;
- TCP and verified TLS connections;
- filesystem and DNS operations;
- safe subprocess execution using argv, without a shell;
- signal watchers.
Subprocesses receive their own Unix process group. On timeout, Pam sends TERM, applies KILL after a bounded grace period, and reaps the process so descendants and zombies are not left behind.
Amp Futures can be passed to Pam\Async\await(). Revolt remains driven by the package's own driver for compatibility; use Pam's native operations on the hottest paths.
See Async runtime for the execution model.
Streaming responses are incremental. A bounded Rust channel sits between the PHP generator and the network transport, so a slow client cannot cause unbounded PHP-side buffering.
$app->get('/events', static function ($request, $response) {
return $response->sse((static function (): Generator {
for ($sequence = 1; $sequence <= 10; ++$sequence) {
yield [
'sequence' => $sequence,
'time' => microtime(true),
];
Pam\Async\delay(0.5);
}
})());
});Pam\Stream\Readable, Writable, and Duplex enforce a highWaterMark. Client disconnects cancel the request Fiber and execute its cleanup path.
Install pushinbr/pam-socket; HTTP and RFC 6455 WebSockets then share the same runtime listener:
use Pam\Socket\Server as SocketServer;
use Pam\WS\Socket;
$io = SocketServer::create();
$io->auth(static fn (array $context): bool =>
($context['headers']['authorization'] ?? '') === 'Bearer secret'
);
$io->on('connection', static function (Socket $socket) use ($io): void {
$socket->join('lobby');
$socket->emit('welcome', [
'id' => $socket->id,
'resumeToken' => $socket->resumeToken,
]);
$socket->on('chat.message', static function (array $data, $ack) use ($io): void {
$io->to('lobby')->emit('chat.message', $data);
$ack->send(['accepted' => true]);
});
});
$app->listen(3000);The real-time layer includes:
- rooms and broadcasts;
- acknowledgements;
- text and binary frames;
- heartbeat and idle timeouts;
- bounded outbound queues and backpressure metrics;
- RFC 7692
permessage-deflate; - authentication hooks;
- connection and message limits;
- HMAC-authenticated session resume tokens;
- Redis Streams and NATS adapters for multi-worker or multi-node broadcast.
The event envelope is intentionally simple:
{"id":"ack-1","event":"chat.message","data":{"text":"hello"}}The protocol is inspired by the ergonomics of Socket.IO, but it is not Engine.IO. Use a standard WebSocket client; Socket.IO clients are not wire-compatible.
Long-lived applications must not leak request state into the next request. Pam handles runtime-owned state and gives application code an explicit scope for everything else:
use Pam\Runtime\RequestScope;
$scope = RequestScope::current();
$scope->set('tenantId', 42);
$handle = $scope->manage(fopen('/tmp/pam-app.log', 'ab'));
$scope->defer(static function (): void {
// Roll back a transaction, release a lock, or close an external context.
});Cleanup runs in LIFO order after success, exceptions, cancellation, timeout, or disconnect. Sampled leak detection compares PHP memory and resource counts before and after cleanup and exposes anomalies through diagnostics and metrics.
The memory integration test intentionally creates cyclic object graphs and abandoned Futures across 10,000 requests after warm-up. The latest optimized validation measured a 51 MiB RSS baseline and high-water mark with 0 MiB sustained growth. A separate mixed Laravel/package contract held an 82 MiB baseline, high-water mark, and final RSS across 2,000 measured requests after warm-up.
Application code is still responsible for its own persistent globals and singletons: never retain a Request, Response, authenticated user, transaction, or tenant in static state.
Laravel managers, facades, and parts of its container are process-global. The
binary-owned Laravel host therefore enforces exactly one active PHP request or
Socket callback per worker and scales with pam start --workers N. This is a
correctness boundary, not a tuning suggestion. Native Pam applications can keep
multiple suspended request Fibers in flight inside one worker.
Development is one process:
pam dev index.phpProduction is a supervised cluster:
pam start index.php \
--workers 10 \
--max-requests 10000000 \
--admin-address 127.0.0.1:3010The production master:
- starts workers with
SO_REUSEPORT; - replaces crashed workers with exponential backoff;
- monitors request deadlines outside the Zend Engine;
- kills and replaces a worker stuck inside blocking PHP;
- staggers worker recycling to avoid synchronized churn;
- drains on
SIGTERM; - performs readiness-gated generational reload on
SIGHUP; - keeps the healthy generation alive when replacement boot fails;
- serves health and aggregate metrics outside PHP.
curl --fail http://127.0.0.1:3010/live
curl --fail http://127.0.0.1:3010/startup
curl --fail http://127.0.0.1:3010/ready
curl --fail http://127.0.0.1:3010/metricsThe listener is disabled unless --admin-address is present. Non-loopback
addresses require a 32–256 character PAM_ADMIN_TOKEN or a bounded regular
secret file selected by PAM_ADMIN_TOKEN_FILE; all endpoints then
require Authorization: Bearer, and pam top forwards the token automatically.
The master removes it from PHP worker environments. Do not expose the admin
listener directly to the public Internet.
$app->listen(443, '0.0.0.0', [
'tlsCert' => '/etc/pam/fullchain.pem',
'tlsKey' => '/etc/pam/private-key.pem',
'http3' => true,
'maxBodyBytes' => 2 * 1024 * 1024,
'maxHeaderBytes' => 32 * 1024,
'maxHeaders' => 100,
'headerReadTimeoutMs' => 10_000,
'bodyReadTimeoutMs' => 30_000,
'requestTimeoutMs' => 30_000,
'maxConcurrentRequests' => 4096,
'responseStreamQueueCapacity' => 16,
'maxResponseBytes' => 256 * 1024 * 1024,
'maxResponseChunkBytes' => 1024 * 1024,
'rateLimitPerSecond' => 200,
'corsOrigins' => ['https://app.example.com'],
'trustedProxies' => ['127.0.0.1'],
'exposeErrors' => false,
'websocketMaxConnections' => 10_000,
'websocketMaxMessageBytes' => 1024 * 1024,
'websocketQueueCapacity' => 256,
'websocketHeartbeatMs' => 15_000,
'websocketTimeoutMs' => 45_000,
'websocketCompression' => true,
'websocketResumeSecret' => getenv('PAM_WS_RESUME_SECRET'),
'websocketResumeTtlSeconds' => 86_400,
'telemetryHeaders' => true,
'accessLog' => true,
'accessLogSampleRate' => 100,
'gcCollectCyclesEvery' => 256,
'gcMemCachesEvery' => 1024,
'leakDetectionSampleRate' => 1024,
'leakThresholdBytes' => 8 * 1024 * 1024,
]);TLS uses Rustls and negotiates HTTP/2 through ALPN. With TLS and http3 enabled, Pam also listens for QUIC on UDP at the same port and advertises Alt-Svc from HTTP/1.1 and HTTP/2 responses.
Defenses include bounded request and response bodies, bounded streaming chunks, header/body deadlines, slowloris protection, token-bucket rate limiting, CORS policy, trusted proxy controls, TLS verification in the HTTP client, redirect credential stripping, strict response-header validation, and bounded process/stream output. A streaming response that crosses its byte limit is failed at the transport boundary instead of being reported as a successful complete body.
Enable telemetry only where you need it; the default hot path stays lean.
- Prometheus metrics for requests, errors, latency, bytes, active requests, WebSockets, backpressure, event-loop lag, RSS, PHP memory, and Fibers;
- W3C
traceparent, request IDs, andServer-Timingresponse headers; - sampled JSON access logs, with all 5xx responses retained;
- PSR-3 logging when
psr/logis installed; - OpenTelemetry context and spans when its Composer packages are installed;
- structured event ring buffer;
- heap, Fiber, connection, profiling, and tracing snapshots.
pam top http://127.0.0.1:3010 --iterations 60 --interval-ms 1000
pam diagnostics index.php
pam heap index.php
pam fibers index.php
pam connections index.php
pam profile index.php
PAM_TRACE=1 pam trace index.phppam index.php [arguments...] run a PHP script or server
pam -r <code> [arguments...] execute inline PHP
pam exec index.php [arguments...] explicitly run a PHP script
pam composer [arguments...] run verified Composer inside Pam
pam extensions [path] [--no-dev] [--json|--toml] explain or pin locked PHP extension requirements
pam dev [index.php] [arguments...] recursive hot reload
pam start [index.php] --workers N supervised production cluster
pam up [index.php] --name api --workers N start and detach a managed application
pam up ... --php-extension iconv --php-extension mbstring
isolate an explicit extension set
pam up ... [--restart-delay-ms N] [--restart-backoff-max-ms N]
[--max-unstable-restarts N] [--min-uptime-ms N] [--no-autorestart]
pam up ... [--shutdown-timeout-ms N] bound graceful stop before SIGKILL
pam up ... --env-file .env.production load a private app environment
pam up ... --health-check-url http://127.0.0.1:8080/health
[--health-check-interval-ms N] [--health-check-timeout-ms N]
[--health-check-start-period-ms N]
[--health-check-failures N] recover live but unhealthy masters
pam ps list managed applications
pam reload api zero-downtime generational reload
pam logs api --errors --lines 200 inspect retained manager logs
pam logs api --both --follow follow stdout and stderr across rotation
pam logs api --both --include-rotated --query ERROR --lines 500 --json
pam daemon start|status|stop control the private per-user supervisor
pam scale api 8 persist and apply a new worker count
pam save && pam resurrect save and restore the desired process list
pam startup --print|--install configure the systemd user service
pam monit [--json] inspect process health and capacity
pam monit:history [name] [--json] inspect bounded one-minute history
pam dashboard [pam-dashboard.html] create a private static health snapshot
pam dashboard:start --token-file TOKEN start authenticated live local view
pam dashboard:status|dashboard:stop inspect or stop the live view
pam config:check [pam.toml] [--json] validate declarative multi-service config
pam plan [pam.toml] [--json] preview reconciliation without mutation
pam apply [pam.toml] [--json] reconcile all declared applications
pam up ... [--memory-warning-bytes N] [--task-warning-count N]
[--memory-max-bytes N] [--task-max-count N] enforce cgroup-v2 limits
pam deploy api /srv/api/releases/2026-08-21 activate a readiness-gated release
pam deploy:history api [--json] inspect bounded release history
pam rollback api [--steps N] restore a previous healthy release
pam traffic:start edge --listen IP:PORT --stable IP:PORT [--tls-cert FILE --tls-key FILE]
pam traffic:set edge --candidate IP:PORT --weight-bps N
pam traffic:evaluate edge --min-candidate-requests N --max-candidate-error-bps N
pam traffic:status edge [--json] inspect per-version rollout evidence
pam traffic:abort|traffic:promote edge atomically finish a rollout
pam test [directory] [--pest|--phpunit] test inside the Embed SAPI
pam routes [index.php] inspect registered routes
pam inspect [index.php] inspect PHP, INI, ABI, and extensions
pam diagnostics [index.php] complete runtime snapshot
pam heap|fibers|connections [index.php] focused diagnostic views
pam profile|trace [index.php] profiling and structured events
pam top [admin URL] live cluster metrics
pam doctor [directory] compare CLI, Embed, and Composer
pam doctor --json|--schema emit diagnostics or its embedded contract
pam doctor --validate doctor-report.json verify a saved report offline
pam benchmark http://host/path built-in HTTP benchmark
pam init [directory] --template raw|http|laravel|desktop|native|native-ui|product
scaffold and install a project
pam init [directory] --template http --socket add native Socket support
pam init [directory] --no-interaction accept the default API preset
pam build [directory] --entry index.php --output dist
pam plan and pam apply fingerprint each declared service's kind, canonical
working directory, canonical script target, and arguments. Structural drift is
reported as action code 8; apply performs a validated replacement and restores
the previous online definition if the new process fails readiness. Existing
records without a declarative identity are replaced once on their next apply.
See Process manager for the full contract and JSON
fields.
pam dev watches PHP files, .env, composer.json, and composer.lock, while ignoring heavy/generated directories. A syntax error does not kill the watcher; fix the file and save again.
pam doctor compares PHP version and ABI, ZTS mode, debug mode, integer size, loaded INI files, CLI/Embed extensions, Composer autoloading, and platform requirements. Environment drift is reported instead of hidden.
pam doctor --schema prints the strict, versioned JSON Schema used by automation without requiring a network connection.
pam doctor --validate applies bounded structural and semantic validation to a saved report and rejects unknown fields, inconsistent codes, oversized inputs, and symlinks.
Pam is built around a short hot path: Rust handles transport and protocol work, the PHP application remains in memory, logging and telemetry are opt-in, and workers scale across CPU cores.
A local development run against a trivial in-memory endpoint produced:
wrk -t4 -c1000 -d10s http://127.0.0.1:3000/
Latency 2.32ms average
Requests/sec 404,150.56
Transfer/sec 53.96MB
Workers 10
This is a single-machine development result, not a universal performance claim. Hardware, kernel settings, handler work, TLS, response size, extensions, logging, and client topology all matter. Measure your own application:
pam benchmark http://127.0.0.1:3000/api/ping \
--requests 200000 \
--concurrency 1000
wrk -t4 -c1000 -d30s http://127.0.0.1:3000/api/pingOptimize for tail latency and stability under realistic handlers—not only maximum requests per second from an empty route. For Laravel, use the reproducible protocol in benchmarks/README.md. Pam does not claim to beat FrankenPHP or Swoole until the same application, worker count, hardware and response contract have been measured under that protocol.
pam composer install --no-dev --classmap-authoritative
pam build --entry index.php --output dist
./dist/bin/pam-runThe bundle contains:
dist/
├── app/ application source and vendor/
├── bin/pam optimized runtime
├── bin/pam-run isolated launcher
├── lib/libphp*.so exact linked PHP Embed ABI
└── manifest.json size and SHA-256 for every packaged file
The builder refuses to overwrite its destination, escape the project through .., package unsafe symlinks, include the output recursively, or bundle a Composer project without an installed autoloader.
Bundles are relocatable, but the target still needs a compatible Linux system ABI and the native dependencies required by PHP and its enabled extensions. Use the provided Dockerfile when you need a controlled userspace as well.
Production assets also include:
- a multi-stage, non-root
Dockerfilewithtini; - a hardened systemd unit in
packaging/pam.service; - x86_64 and ARM64 release workflows;
- SHA-256 release files and build attestations;
- CI, security audit, memory soak, and Valgrind workflows.
Read Production operations before deployment.
┌──────────────────────────────┐
│ master control plane │
│ health · metrics · watchdog │
└──────────────┬───────────────┘
│ supervises
SO_REUSEPORT ▼
client ──► TCP / TLS / QUIC ──► worker ──────────────────────────────┐
│ │
├─ Rust: HTTP 1/2/3 + WebSocket │
├─ Tokio: sockets, timers, I/O │
└─ Zend Engine: loaded once │
│ │
request Fiber │
│ PHP handler │
│ suspend typed operation ─────┤
│ │
└──────── resume with result ◄─┘
One Zend NTS engine belongs to one worker thread. No callback enters PHP from a foreign thread. Rust validates request and response boundaries, while the C shim keeps the native ABI intentionally narrow and versioned.
The boot sequence is:
- initialize PHP Embed with the real process arguments;
- discover and load Composer once;
- load Pam's runtime modules;
- execute the application entrypoint once to register routes and callbacks;
- open the network listeners;
- dispatch each request in an isolated Fiber;
- suspend to Tokio for native waits and resume the exact same Fiber;
- deterministically clean request state and periodically release allocator caches.
Read Architecture and the versioned Native API for internals.
The repository's release gate is intentionally strict:
cargo fmt --all -- --check
cargo clippy --locked --all-targets --all-features -- -D warnings
cargo test --locked --all-targets --all-features -- --test-threads=1
compat/composer-smoke/vendor/bin/phpstan analyse \
-c compat/composer-smoke/phpstan.neon --no-progress
compat/laravel-smoke/vendor/bin/phpstan analyse \
-c compat/laravel-smoke/phpstan.neon --no-progress
compat/composer-smoke/vendor/bin/phpunit \
-c compat/composer-smoke/phpunit.xml
./target/debug/pam test compat/composer-smoke --phpunit --colors=never
./target/debug/pam test compat/composer-smoke --pest --colors=never
pam composer audit --working-dir=compat/composer-smoke --locked
pam composer audit --working-dir=compat/laravel-smoke --locked
cargo audit
composer --working-dir=packages/octane verify
scripts/package-release.sh validateThe integration suite starts real servers and covers CLI behavior, hot reload, master/worker supervision, crash recovery, worker recycling, failed and successful reloads, watchdog replacement, REST, traditional PHP state, PSR contracts, multipart uploads, sessions, TLS, HTTP/2, HTTP/3, slowloris defense, request/response limits, CORS, rate limiting, metrics, WebSockets, fragmented NATS frames, native I/O, concurrent suspended Fibers, streaming backpressure/cancellation, Laravel binary ranges, SSE, a reentrant HTTP client, Composer packages, and memory/RSS stability.
Release evidence is generated by CI and the checked-in benchmark protocol. Do not copy historical test counts, audit totals or soak results into a new release; publish the output for the exact clean commit being tagged.
Pam is ambitious, but the boundaries matter:
- One PHP thread per worker. Multiple requests can remain in flight and interleave at suspension points, but only one executes PHP bytecode at a time inside a worker. Scale CPU with
pam start --workers N. - Laravel is serialized per worker. PAM forces
maxConcurrentRequests=1for the persistent Laravel host because framework managers and facades are process-global. Add workers for concurrency; PAM refuses an unsafe override. - Blocking extensions still block. Pam does not transparently replace every
php_streamor syscall hidden inside arbitrary extensions. Use native Pam I/O, a cooperative Composer library,ProcessPool, or additional workers. - Third-party event loops are compatibility bridges. Amp/Revolt work through Fiber integration, but their selected driver is not transformed into Tokio. It can occupy a worker while running.
- Direct-mode watchdog limits. A synchronous callback can only be identified as late after it returns in direct mode. Production
startmode enforces the deadline externally and replaces a stuck worker; the in-flight request is lost and should be retried only when safe/idempotent. - WebSocket reloads require reconnect. Generational reload drains the old worker, but persistent clients must reconnect and may use
sessionIdplusresumeToken. - Socket.IO is not implemented. Pam speaks standard RFC 6455 WebSocket, not Engine.IO.
- HTTP/3 currently covers request/response. WebSockets continue to use the HTTP/1.1 upgrade path; WebTransport and WebSocket over HTTP/3 are not implemented.
- Persistent application rules apply. Frameworks and packages must not retain request-specific state in globals or singletons.
- Host scope is Linux and macOS. Windows users can use WSL for server projects; native Windows host binaries are not part of 1.0.
Stable contracts do not remove application-specific risk. Production adoption should still begin with staging, soak tests, representative traffic and a tested rollback plan.
- PHP should still feel like PHP. Elegant APIs, strict types, familiar request/response semantics.
- Composer is non-negotiable. No competing package manager and no ecosystem fork.
- Performance must be structural. Persistent boot, bounded queues, native transport, controlled observability.
- Safety beats benchmark theater. Deadlines, limits, cancellation, cleanup, audits, and honest measurements.
- The native boundary stays small. Version the ABI, validate both sides, never call Zend from the wrong thread.
- Production is part of the runtime. Health, metrics, reload, recovery, packaging, and diagnostics are core features.
Pam is early enough that excellent contributions can still shape its architecture.
Before submitting a change:
- keep public PHP APIs small, typed, and readable;
- preserve Composer and PSR compatibility;
- add limits and cancellation to every potentially unbounded operation;
- add regression coverage for the behavior;
- run the complete validation gate above;
- document new capabilities and their production trade-offs.
Bug reports are most useful with the Pam version, PHP Embed version, pam doctor output, minimal application, expected behavior, actual behavior, and a reproducible command.
See CONTRIBUTING.md for the development workflow, GOVERNANCE.md for decision-making and ROADMAP.md for current direction. Usage support is described in SUPPORT.md; vulnerabilities follow SECURITY.md.
PAM is free and open-source software under the Apache License 2.0. You may use, modify, and distribute it—including commercially—subject to the license terms. See the plain-language licensing guide.
Keep the language. Keep the ecosystem. Upgrade the runtime.