# Respira CLI architecture: six-phase execution cycle

> How Respira CLI works under the hood. A WordPress-native execution cycle with five framework hooks, tool chain functions, and structured tracing. v0.1 ships the scaffolding, v0.2 adds callback registration.

Source: https://www.respira.press/cli/docs/architecture

---

**New to the CLI?** Start at [respira.press/cli](https://www.respira.press/cli) for install, the command surface, and the agent workflow. This page is for people who want to know what is happening under the hood. 

## The execution cycle

Every invocation of `respira` moves through six named phases, in this order, every time: 

1. **LoadContext**: gather invocation context. Auth, target site, switches, task input.
2. **PreHooks**: fire the `before_resolve` framework hook.
3. **Resolve**: select and validate the Tool Chain Function that will run.
4. **Execute**: run the selected function. This is where the SDK call happens.
5. **PostHooks**: fire `filter_result` and `after_execute` framework hooks.
6. **Return**: normalize the result, emit the trace if `--verbose`, and return.

Nothing skips phases. Nothing reorders them. Deterministic by design, traceable with `--verbose`. If a phase throws, the error is captured into a `CycleError` with the phase name attached and returned as `CycleResult.error`. The cycle never re-throws at its boundary. 

## The hook framework

Five framework-level hook points are live in v0.1: 

- `before_resolve`: action. Observe the invocation before resolution. Log it, enforce policy.
- `filter_plan`: filter. Transform the candidate tool chain function list.
- `before_execute`: action. Final gate before the tool runs.
- `filter_result`: filter. Transform the tool's output on the way back.
- `after_execute`: action. Record, notify, trigger follow-on work.

In v0.1 no callbacks register here. The contracts are frozen. v0.2 adds **callback registration** and **extension manifests** on top, without changing anything you see in v0.1\. If you are building against respira today, nothing you write will need to change when v0.2 arrives. 

## Tool Chain Functions

Every command is a typed function with metadata: 

```
interface ToolChainFunction<T> {
  name: string;                    // "read.structure"
  description: string;
  domainTags: readonly string[];   // ["pages", "read", "public", "anonymous"]
  capability: "read" | "write" | "destructive";
  prerequisites: readonly Prerequisite[];
  internalHooks?: readonly HookDeclaration[];
  execute(input: unknown, ctx: CycleContext): Promise<T>;
}
```

Domain tags document what a function does across four dimensions: builder (elementor, divi, bricks, gutenberg, etc.), domain (pages, posts, media, design-system), capability (read, write, destructive), and access (public, anonymous, connected, licensed). In v0.1 these are metadata; in v0.2 they become SQL filters against the Tool Registry. 

## File-organization convention

There are 34 public commands, and exactly 34 files under `packages/cli/src/commands/`. One `BaseCommand` subclass per file, one `ToolChainFunction` per file, always co-located. No topic-level grouping, no shared files. This is frozen. 

- The `ToolChainFunction` is a **named export** alongside the oclif `Command` class (which stays the default export). Name pattern: `<camelCasePath>Function`.
- Examples: `auth/login.ts` exports `authLoginFunction`. `read/design-system.ts` exports `readDesignSystemFunction`. `write/create-page.ts` exports `writeCreatePageFunction`.
- The function's `name` field uses dot-notation mirroring the route (`auth.login`, `read.design-system`, `write.create-page`). Traces, logs, and (v0.2) the extension manifest reader key on this.
- v0.2 discovers every function by globbing `src/commands/**/*.ts` and picking up named exports ending in `Function`. No user file changes when the registry swaps in.

## Structured tracing

Pass `--verbose` on any command and the cycle writes a JSON trace to `~/.respira/traces/{invocationId}.json`. Each entry carries a phase, event type, timestamp, duration, and status. The trace is for coding agents that need to understand why something did or did not do what was expected. 

```
$ respira read structure https://example.com --verbose
[respira] trace: ~/.respira/traces/2adcfb73-8a97-4b94-be23-1448f2db1cce.json
```

Without `--verbose`, traces accumulate in memory and are discarded at end of command. No stderr noise. 

## Error handling

The cycle catches phase errors and wraps them in a `CycleError` with codes like `PREREQUISITE_NOT_MET`, `VALIDATION_FAILED`, `TOOL_CHAIN_ERROR`, `PHASE_ERROR`. The original error is preserved as `cause`, so command handlers unwrap it for user display. What you see on the terminal is the familiar RespiraError message and hint. The CycleError context is logged only when `--verbose` is set. 

## What ships in v0.1, what ships in v0.2

**In v0.1:**

- The execution cycle. Six phases. Deterministic. Traceable.
- The hook framework contracts. Five named hooks. Frozen shape.
- Tool Chain Function abstraction for all 37 commands, co-located with each `BaseCommand` subclass.
- Null hook registry. The hook-firing code path runs, callbacks are always empty. This is the scaffolding that makes v0.2 purely additive: `NullHookRegistry` swaps for `ManifestBackedHookRegistry`, callbacks start registering, no existing caller changes.
- Structured tracing with `--verbose`.

**In v0.2:**

- Callback registration against framework-level hooks.
- Extension manifests for third-party contributors.
- SQLite-backed tool registry with full-text search.
- Tool chain functions declaring internal hook points.
- Baseline site inventory for per-site prerequisite validation.
- Priority resolution when callbacks run in order.

Every v0.2 addition is additive. No breaking changes. No "we renamed this" in the changelog. That is the whole point of landing this scaffolding now. 

## Why this architecture, why now

The CLI works today. 37 commands, 45-tool catalog, typed SDK, green CI. Publishing as-is would be reasonable. But once extensions and third-party integrations exist, they will register callbacks against phase boundaries that do not currently exist. Retrofitting those boundaries later is breaking work. Landing them now, while nothing has subscribed to them, is cheap and costs nothing downstream. 

The part most users care about: none of this changes how you use the CLI. Same commands. Same flags. Same output. The architecture is there because v0.2 is going to need it, and retrofitting it later would break everyone who adopted v0.1\. 

**Next:** [the hook framework in detail](https://www.respira.press/cli/docs/hooks), or [browse commands](https://www.respira.press/cli/docs/commands).
