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(), sendStructured(), stream(), conversation(),
sendInConversation(), streamInConversation() or resume() 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, 127.0.0.1 or [::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 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. When the
manifest’s config sets custom_events, cloud listens to every event instead of the listed ones, so
any event phpClaw or your code fires is posted, including run.*, memory.search,
memory.recalled and compaction.*. An event with no payload rule of its own is sent with its whole
context under payload.
The four graph events have their own rule and send flat fields only, never the graph state:
graph.start sends entry_node, graph.end sends steps and last_node, graph.fan_out sends
node and branches, and graph.join sends node, branches and failed. They, and the
agent.before, agent.iteration, agent.after and agent.max_iterations payloads, carry
parent_run_id, which links the event to its parent run (for an agent called from a graph node,
the graph’s run), or null when there is none. See Pipelines and graphs.
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. A field under any other name is sent as it is, so
the summary of compaction.after still arrives in full.
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”The WordPress, Joomla, Drupal, Magento, PrestaShop, OpenCart, Laravel and Symfony adapters start cloud only when their Store Messages setting is on, so turning that off keeps every event and every scan local. The CakePHP, CodeIgniter and Yii adapters start it whenever a cloud key is set, whatever their store messages setting.
Core applies the same check: Claw starts cloud only when a cloud key is set and storeMessages is
on, so storeMessages(false) keeps every event and every scan local even with a cloud key.
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 |