Cloud
phpclaw/phpclaw-cloud is optional. With a cloud key set, it sends lifecycle events to a cloud
endpoint and can register a guard that asks that endpoint to scan each message.
composer require phpclaw/phpclaw-cloudIt requires PHP 8.1, ext-curl, ext-json and phpclaw/phpclaw. Every adapter already depends on it.
Activating it
Section titled “Activating it”use PhpClaw\Claw;
$agent = Claw::builder() ->cloudKey('...') ->build();| Builder method | Effect |
|---|---|
cloudKey(string) | enables cloud; an empty key leaves it off |
cloudDisable(array) | features to turn off (observability, scan) and hide_ names that keep content on your server |
cloudSigningSecret(string) | verify signed scan responses; empty leaves verification off |
Cloud starts on the first send(), stream() or conversation() call, not at build(). If the
package is not installed, a cloud key does nothing: core checks for CloudManager with class_exists()
before booting it.
The endpoint
Section titled “The endpoint”Requests go to https://phpclaw.ai/platform/api, fixed in code as CloudHttp::DEFAULT_BASE_URL. No
environment variable redirects it, so the trace stream cannot be pointed somewhere else.
Requests are made only over https://, or plain http:// to localhost or 127.0.0.1. Any other
URL is dropped before a connection is opened, and nothing is thrown. Redirects are not followed.
Every request carries Authorization: Bearer <cloud key> and X-PhpClaw-Version: 1.0.
The loopback list in the code also holds ::1, but a URL for it must be written http://[::1]:8080/,
whose parsed host is [::1], so an IPv6 receiver is dropped like any other host.
The manifest
Section titled “The manifest”On start, cloud fetches /v1/manifest, which lists the events to forward and the guard features to
enable. It is cached for one hour in the system temp directory, signed with an HMAC keyed by the cloud
key and written with mode 0600. While that cache is still fresh, start-up makes no request at all.
A cache that was edited, or signed with another key, is treated as missing rather than trusted. When
the fetch fails, the last cached manifest is used at any age; with no cache at all, nothing is
forwarded and no guard is added.
cloudDisable() takes feature names from the manifest. Disabling a feature drops every event it
groups and its guard. The manifest names two:
| Name | Turns off |
|---|---|
observability | every lifecycle event; nothing is posted to /v1/runs |
scan | the scan guard; no message is posted to /v1/scan |
To send nothing at all, disable both, or leave the cloud key unset. cloudDisable() also takes the
three hide_ names described in Keeping content on your server,
which keep tracing on but leave the content out.
What is sent
Section titled “What is sent”Lifecycle events
Section titled “Lifecycle events”Enabled events are posted to /v1/runs, except provider.token, which is never sent.
Posting does not wait for a reply. Each request has a 500 millisecond limit, is pumped for at most 50 milliseconds when the event fires, and any still pending are finished when PHP shuts down.
| Rule | Result |
|---|---|
a field named password, passwd, secret, secret_key, private_key, api_key, apikey, api_token, access_token, auth_token, token or authorization | value replaced with [redacted] |
| a string over 8192 bytes | cut, ending …[truncated] |
| an array over 100 items | replaced whole by the string [array-too-large:N] |
| the whole payload over 64 KB | replaced by event, ts, run_id, oversized: true and original_bytes, with no content |
Redaction matches the whole field name, ignoring case. api_key and API_KEY are redacted;
my_api_key is not, and neither is a secret that appears inside a string, such as a password in an
SQL query or a message.
Keeping content on your server
Section titled “Keeping content on your server”Three more names in cloudDisable() still send every event, but without its content. The field keeps
its key and arrives as [hidden]; a field that was never set stays empty.
$agent = Claw::builder() ->cloudKey('...') ->cloudDisable(['hide_inputs', 'hide_outputs']) ->build();| Name | Fields sent as [hidden] |
|---|---|
hide_inputs | message, prompt, message_excerpt, tool_input, command |
hide_outputs | text, response, tool_result |
hide_metadata | metadata |
Fields are matched at any depth and ignoring case, so the nested payload of a custom event is
covered too. Everything else still arrives: event names, run ids, timings, token counts, status and
tool names, so runs are still traced and counted.
hide_inputs also leaves out the scan guard, because the scan posts the message. error fields are
never hidden. A misspelled name such as hide_input is ignored like any other unknown name, so
nothing is hidden.
The scan guard
Section titled “The scan guard”When the manifest enables it, CloudScanGuard is registered on the guard chain at the priority the
manifest gives, or 50. For each message it posts the text to /v1/scan and blocks when the reply says
so. It is not registered when hide_inputs is set.
It scans the augmented message, so memory and skill text added to the prompt is sent too.
| Situation | Result |
|---|---|
| the endpoint cannot be reached | the message is allowed |
| a signing secret is set, and the reply’s signature is missing or wrong | the message is blocked |
the reply is allowed: false | the message is blocked with the reply’s reason |
The signature is an HMAC-SHA256 over allowed, reason and request_id, keyed by the signing
secret.
CloudManager::boot() also takes $failClosed, which blocks messages when the endpoint is
unreachable. The builder does not pass it, so through Claw::builder() the guard always allows on an
outage.
In the adapters
Section titled “In the adapters”Every adapter starts cloud only when its Store Messages setting is on, so turning that off keeps every event and every scan local.
That check lives in the adapters. In core, a cloud key alone is enough: there is no storage setting in between.
Classes
Section titled “Classes”| Class | Role |
|---|---|
CloudManager | boot($key, $disable, $signingSecret, $failClosed): fetches the manifest and registers the hook and guard, once per key |
CloudHttp | the HTTP client: get, post and non-blocking fire; failures return nothing and are not thrown |
CloudManifest | the enabled events and guard features |
CloudPayloadBuilder | builds and reduces each event payload |
CloudWebhookHook | the hook that sends events |
CloudScanGuard | the guard that calls the scan endpoint |