20: For AI Agents
Phlo is built to be driven by AI agents, not just read by them. Every app exposes three CLI layers that let an agent work without prior knowledge of the codebase: reflect:: to understand it, phlo_eval to execute against it, and build:: to compile and verify. All three require build: true and must never run against production.
If you are an agent picking up a Phlo app, start here.
20.1: The triad: understand, execute, compile
Three layers, three planes. Reach for them in this order.
| Layer | Plane | Use it to |
|---|---|---|
reflect:: |
Static | Understand structure without running anything: routes, views, the parsed AST, search, dependency graphs. Safe, read-only, works even when the runtime is broken. |
phlo_eval |
Runtime | Execute a string of Phlo against the live app: read records, call a method, render a view, reproduce a bug. Powerful and side-effecting. |
build:: |
Compile | Build .phlo sources to PHP and lint the result before and after editing. |
reflect:: and build:: run outside the app (source only, no boot), which is why they are fast and side-effect-free. phlo_eval runs inside the booted app, which is why it can touch live data, and why it carries real risk.
20.2: Recommended workflow
- Orient.
reflect::contextfor a one-call snapshot: identity, route/view counts, loaded packages, recent errors. Thenreflect::compactRoutes. - Explore.
reflect::find <type>,reflect::search <query>,reflect::nodeBody <name>,reflect::fileContent <relPath>. - Execute.
phlo_eval '<phlo>'to check live data and behaviour: a count, a record, a method's real output, a rendered view. - Read. Read the actual
.phlosource before editing. - Edit. Only
.phlo,data/app.jsonand entrypoints. Never the generated PHP. - Build and lint.
build::run, thenbuild::lint(empty array = clean; if not, fix the source and repeat). - Record. Update
data/app.mdwith structural changes, new routes, resolved TODOs and remaining work.
20.3: reflect:: in practice
php www/app.php reflect::context # orient
php www/app.php reflect::compactRoutes # route map
php www/app.php reflect::search "createRecord" all # find usage
php www/app.php reflect::nodeBody home # one method's body
php www/app.php reflect::objectIndex # resources, methods, props
Output is JSON. reflect::context reads data/app.md, the human- and agent-written scratchpad that orients you on intent and status. See the Tooling chapter for the full command reference.
20.4: phlo_eval in practice
phlo_eval '<phlo source>' transpiles a string of Phlo statements and runs them in the live app. It is the runtime complement to reflection: where reflect:: reads the static source, phlo_eval executes against real data, resources and views.
php www/app.php phlo_eval "user::recordCount()" # 38, no return keyword
php www/app.php phlo_eval "%app->title" # "App"
php www/app.php phlo_eval "echo %app->error('boom')" # <div class="app-error">boom</div>
A single expression needs no return: it auto-returns like a => arrow body. Reach for return only inside a multiline block, where it is required:
php www/app.php phlo_eval '$pro = array_filter(user::records(), fn($u) => $u->tier === "pro")
return array_values(array_column($pro, "email"))'
Rules:
- Auto-return. A single line auto-returns, exactly like a
=>arrow body, unless it starts withreturn/apply/echo/unset/yield. A multiline block needs its ownreturn. - Output.
return <expr>prints the value as JSON: type-safe, non-scalar-capable, and errors come back as{"error": ...}.echo <expr>prints raw to stdout, for viewing rendered HTML/markup as-is. - Scope.
%resourcerefs resolve as normal. The app is constructed but its app-controller (router/init) is skipped, like any CLI callback; resource controllers do run, so the database and resources are live. - Errors are readable. A failing call returns a structured
{"error": ...}with type, message and source, so you can iterate: one run teaches you thatuser::records()returns objects, the next uses->tierinstead of["tier"].
20.5: Power and danger
phlo_eval runs arbitrary code with full app privileges against the live app. In one call it can read password hashes, API keys and personal data, and in another it can mutate or delete data (%db->exec(...)). There is no sandbox.
That is exactly why it is constrained:
- CLI-only. It refuses to run during an HTTP request, even if wired into a route. It can never become a network-reachable eval endpoint.
- Build-only. It exists only when
build: true. It is never compiled into a release or production app; calling it there is simply an undefined function. - Developer-authored only. Treat its input like
eval()itself: write it yourself, never pass it untrusted or user-supplied input.
Within those limits it is a precise, honest tool: it shows you what the app actually does, not what the source suggests it might.
20.6: build:: in practice
php www/app.php build::run # compile changed .phlo to PHP
php www/app.php build::lint # [] means clean; otherwise fix the .phlo source
php www/app.php build::release # compile the release build when stage/prod output changes
Lint reports parse errors in the compiled PHP. Always fix the .phlo source and rebuild; never patch the generated PHP. See the Tooling chapter for the full command reference.