# Tutorial: Technical Track

This file is loaded by `.claude/skills/tutorial/SKILL.md` when the user selects the technical
track. Do not invoke this file directly -- the router reads it and dispatches to the lesson the
user picks.

Each lesson is a script for the teaching agent. Instructions in plain text address the agent
("Read X", "Walk the user through Y"). User-facing strings are indented as block quotes.

After each lesson, update `.claude/memory/tutorial-progress.md` by changing `[ ]` to
`[x] YYYY-MM-DD` for that item. After completing any full section, redisplay the menu with
updated markers.

---

## Menu

```
SECTION A -- The workspace                              (3 items)
  1  What is this workspace                              [ ]
  2  The skill layer -- slash commands you'll use        [ ]
  3  The vibe layer -- ambient intent classification     [ ]

SECTION B -- Build your first thing                     (5 items)
  4  Echo workflow tour                                  [ ]
  5  Custom workflow with schemas                        [ ]
  6  Platform tools and credentials                      [ ]
  7  Multi-step and conditionals                         [ ]
  8  Going to production                                 [ ]

SECTION C -- The Organization Model                     (3 items)
  9  /om ceremony -- identity, customers,         [ ]
     offerings via the layered flow
 10  Systems, actions, and labels                        [ ]
 11  Entity extensions -- BaseProject, BaseDeal          [ ]

SECTION D -- Modules (load on demand)                   (6 items)
 12  HITL                                                [ ]
 13  Schedules                                           [ ]
 14  Notifications + integrations                        [ ]
 15  Error handling                                      [ ]
 16  LLM and agents                                      [ ]
 17  Composition (execution.trigger)                     [ ]

SECTION E -- Power user                                 (2 items)
 18  Rules, memory, scaffold registry                    [ ]
 19  Template lifecycle and /git-sync                    [ ]
```

---

## SECTION A -- The Workspace

### Item 1: What is this workspace

**Goal:** The user understands the three-layer project structure, the standalone repo model, and
why the scaffold is described as an agent operating environment, not just an app starter.

**Estimated time:** 15 min

**Files referenced:** `.claude/rules/agent-start-here.md`, `CLAUDE.md`,
`node_modules/@elevasis/sdk/reference/scaffold/index.mdx`

**Flow:**

Open by reading `.claude/rules/agent-start-here.md` (the canonical first-read) and `CLAUDE.md`
(the project bootstrap surface). Use both to anchor the explanation below.

Open with the skip affordance:

> "Item 1 covers what this workspace is and how it is structured. If you already know the
> three-layer layout (ui / operations / core), the standalone repo model, and why the scaffold
> is an agent operating environment, type 'skip' and I'll mark it complete."

If the user skips, mark `[x] YYYY-MM-DD` in `.claude/memory/tutorial-progress.md` for item 1
and move on.

Otherwise, walk through the following:

**Three runtime layers.** The template is divided into three directories with hard runtime
boundaries:

- `ui/` -- React 19 frontend (Vite, TanStack Router, Mantine). Browser runtime. Imports from
  `core/` only.
- `operations/` -- Platform workflows and agents (Elevasis SDK). Node target. Imports from
  `core/` only.
- `core/` -- Runtime-agnostic shared contracts: Zod schemas, TypeScript types, organization
  model configuration. No React, no Node APIs, no SDK worker imports.

Emphasize: `ui/` and `operations/` are separate runtimes. They communicate through the platform
API at runtime. At build time they share types only via `core/`.

**Standalone repo.** This project is NOT part of the monorepo pnpm workspace. It has its own
`.git/`, own `pnpm-lock.yaml`, own `node_modules/`. The monorepo boundary hook does not apply
here. Git, gh, and CLI commands work directly.

**Agent operating environment.** Read the "Template Surfaces" section from `agent-start-here.md`
aloud to the user. Key point: the `.claude/` directory is a first-class surface -- rules (always
loaded), skills (invocable via slash commands), hooks (run on tool calls), and memory (persists
across sessions). The scaffold is not just an app starter; the agent reads `.claude/rules/` on
every session and uses it to route task classes, resolve boundaries, and choose the right tool.

**SDK reference scaffold.** After `pnpm install`, the entry point
`node_modules/@elevasis/sdk/reference/scaffold/index.mdx` gives access to canonical recipes, UI
patterns, the gating model, contracts, and a glossary. Whenever the user wants the authoritative
answer on a scaffold surface (how to add a System or UI feature, extend an entity, wire a workflow), that
index is the starting point.

**Verification:** Ask the user: "Where does the agent look first when entering a session?" (Answer:
`agent-start-here.md` via the always-loaded rules.) If they get it right, continue. If not, point
them to the "First Action: Check Active Projects" and "Discovery Order" sections in
`agent-start-here.md`.

**Completion signal:** Mark `[ ] 1 What is this workspace` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 2: The skill layer -- slash commands you'll use

**Goal:** The user knows all 12 slash commands, how to invoke them, where they live, and can
demonstrate `/status` and `/project`.

**Estimated time:** 15 min

**Files referenced:** `.claude/skills/*/SKILL.md` (skim), `CLAUDE.md` slash command table

**Flow:**

Open with the skip affordance:

> "Item 2 walks through the 12 slash commands in this scaffold. If you've already used them and
> know where they live, type 'skip' and I'll mark it complete."

If the user skips, mark complete and move on.

Otherwise, read the Slash Commands table in `CLAUDE.md` aloud. Then explain the skill system:

**Invocation pattern.** Every slash command is a skill. Type `/<name>` (optionally with args,
e.g., `/om systems`). The agent reads `.claude/skills/<name>/SKILL.md` and follows its
instructions. Skills may be context-forked (they run in a subagent) or inline. The frontmatter
`context: fork` means a subagent handles it.

**Where they live.** Each skill lives in `.claude/skills/<name>/SKILL.md`. Multi-step operations
that belong to a skill (but are too large for `SKILL.md`) live in
`.claude/skills/<name>/operations/<op>.md`.

**How the agent knows to use them.** The `description` frontmatter field (or `metadata.promptSignals`)
tells the agent when to auto-invoke a skill without an explicit slash command. For example,
`/om` auto-invokes whenever the conversation references org-model entities.

Walk through each of the 12 commands:

- `/setup` -- first-time bootstrap: placeholder replacement, deps, verification, then hands off
  to `/om`
- `/save` -- conversation fanout: updates knowledge docs, persists `prj_tasks.resume_context`
  via `elevasis-sdk project:task:save`, creates a project note on blockers/status signals
- `/dsp` -- parallel agent dispatch for implementation tasks with multiple independent steps
- `/deploy` -- test, build, commit, push pipeline
- `/explore` -- codebase exploration anchored to docs
- `/git-sync` -- pull latest changes, surface new template sync notes, install when needed,
  run baseline verification, then stop before manual reconciliation
- `/status` -- quick project health check
- `/project` -- primary work-tracking entry point; portfolio orientation, intent detection
  (resume vs new), and project/milestone/task/note lifecycle via `elevasis-sdk project:*`
- `/elevasis` -- SDK operations: check, deploy, exec, describe, logs, creds
- `/sync` -- fresh reinstall and cache reset when local deps or build caches are stale
- `/om` -- org model ceremony: read, codify, toggle, layered flow for identity through
  goals
- `/submit-request` -- submit a structured request (bug, feature, support) to the platform

**Demo 1: `/status`.** Ask the user to type `/status`. Narrate what it shows: project health
summary, active projects, recent executions if any.

**Demo 2: `/project`.** Ask the user to type `/project`. Walk them through the portfolio
orientation output. Show how to create a task: `/project create task --title "My first task"`.

**Verification:** The user can name any 3 commands unprompted and describe what they do.

**Completion signal:** Mark `[ ] 2 The skill layer` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 3: The vibe layer -- ambient intent classification

**Goal:** The user understands the 7-intent classifier, can recognize when it fires, and knows
it is absent in the monorepo.

**Estimated time:** 15 min

**Files referenced:** `.claude/rules/vibe.md`, `CLAUDE.md` ambient vibe section

**Flow:**

Open with the skip affordance:

> "Item 3 covers the vibe layer -- an ambient intent classifier that fires on every natural-language
> message. If you already know the 7 intent types and how the classifier routes them, type 'skip'
> and I'll mark it complete."

If the user skips, mark complete and move on.

Otherwise, read `.claude/rules/vibe.md` before explaining. Then walk through:

**What vibe is.** Every natural-language message in an external project passes through the vibe
classifier before the agent acts. No slash command, no activation phrase. The agent silently
classifies the message into one of seven intent types and routes to the right behavior. The user
never sees the classification.

**The seven intent types.** Walk through each by name with one fixture example:

- **Capture** -- "Add a task to follow up with the Shopify client." Agent drafts, confirms,
  writes via `elevasis-sdk project:*`.
- **Query** -- "What's pending in the HITL queue?" Agent reads and narrates.
- **Describe** -- "What's going on with this deal?" Agent narrates from org model labels.
- **Transition** -- "Done with the proposal draft." Agent confirms, updates task status via
  `elevasis-sdk project:task:save`.
- **Navigate** -- "Let's focus on the onboarding flow." Agent updates scope, narrates the shift.
- **Codify** -- "We track deals by Shopify platform." Agent detects new organizational reality
  and delegates to `/om`.
- **Toggle** -- "Turn on the lead-gen system." Agent delegates to `/om systems`.

**Important behavioral rules.** Codify and Toggle delegate immediately to `/om` -- the
vibe layer detects intent but does NOT run the ceremony itself. The ceremony (snapshot, propose,
confirm, write, validate, rollback) belongs to `/om`. Ambiguous messages get one
clarifying question; no precedence rule is applied.

**Where it applies.** Vibe is always-on in external projects. It is explicitly OFF in the
monorepo. The monorepo uses task-class routing via `agent-start-here.md` instead. If the user is
working inside the monorepo (`apps/`, `packages/`, etc.), vibe does not fire.

**Reference.** The full classifier definition, fixture tables, and codify heuristics live in
`.claude/rules/vibe.md`. The `CLAUDE.md` table is a pointer -- for authoritative behavior, read
the rule file.

**Verification:** Ask "What happens when you type 'turn on SEO'?" Answer: Vibe classifies it as
Toggle intent and delegates to `/om systems` without running the ceremony itself.

**Completion signal:** Mark `[ ] 3 The vibe layer` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

## SECTION B -- Build Your First Thing

### Item 4: Echo workflow tour

**Goal:** The user reads the echo workflow source, validates it, deploys it, and executes it
end-to-end.

**Estimated time:** 20 min

**Files referenced:** `operations/src/example/echo.ts`, `core/types/index.ts`,
`operations/src/index.ts`

**Commands:** `pnpm -C operations check`, `pnpm -C operations deploy`,
`pnpm elevasis-sdk exec echo --input '{"message":"hi"}'`

**Flow:**

Tell the user:

> "Let's tour the echo workflow -- the starter resource that ships with every template project.
> We'll read the source, run the validator, deploy it, and execute it."

**Step 1: Read the source.** Open `operations/src/example/echo.ts` with the user. Walk through
each section:

- `config` block: `resourceId` (the deploy-time unique ID), `name`, `type: 'workflow'`,
  `description`, `version` (semver, bump on contract changes), `status: 'dev'` (new resources
  start here; `'prod'` means visible to end users in the production environment).
- `contract` block: `inputSchema` and `outputSchema` are imported from `core/types/index.ts`,
  NOT defined inline. This is the pattern: schemas live in `core/` so both the frontend and
  `operations/` can import them without creating a dependency cycle.
- `steps` map: each step has `id`, `name`, `description`, a `handler` async function, and
  `next: null` (last step). The handler receives `input` and `context`. `context.logger.info()`
  is the correct logging call -- `console.log` is not captured by the platform.
- `entryPoint`: the key from `steps` that runs first.
- `interface.form`: defines the UI form the Command Center renders when a user triggers this
  workflow manually.

**Step 2: Validate.** Run:

```bash
pnpm -C operations check
```

Show the output. Explain: this catches duplicate `resourceId`s, broken step chains (`next`
referencing a non-existent step), invalid schema serialization, and other errors the platform
would reject at deploy time. Exit code 0 = pass.

**Step 3: Deploy.** Run:

```bash
pnpm -C operations deploy
```

Walk through the output stages: Authenticating, Validating, Bundling (esbuild packages
`operations/src/index.ts` and all dependencies into a single CJS file), Uploading. After deploy,
resources are live immediately -- no restart required.

**Step 4: Execute.** Run:

```bash
pnpm elevasis-sdk exec echo --input '{"message":"hi"}'
```

Show the output: status, duration, and output `{ "echo": "hi" }`. Explain: the platform
validated `{"message":"hi"}` against `echoInputSchema`, spun up an ephemeral worker thread,
ran the handler, returned the result, and terminated the worker.

**Step 5: Check history.** Run:

```bash
pnpm elevasis-sdk executions echo
```

Pick the execution ID from the list and run:

```bash
pnpm elevasis-sdk execution echo <execution-id>
```

Show the full detail including logs. Point out the `[echo] Received message: "hi"` log line
emitted by `context.logger.info()` in the handler.

**Verification:** The user sees the execution complete with `{ "echo": "hi" }` output.

**Completion signal:** Mark `[ ] 4 Echo workflow tour` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 5: Custom workflow with schemas

**Goal:** The user creates a new workflow in its own domain directory, defines a Zod input schema
in `core/types/`, registers it in `operations/src/index.ts`, and validates it.

**Estimated time:** 25 min

**Files referenced:** `operations/src/example/echo.ts` (model), `core/types/index.ts`,
`operations/src/index.ts`

**Commands:** `pnpm -C operations check`, `pnpm -C core check-types`

**Flow:**

Tell the user:

> "Now you'll build a real workflow from scratch. We'll pick a domain for your project, define
> the input and output schemas in `core/types/`, author the workflow in `operations/src/`, and
> register it."

**Step 1: Pick a domain.** Ask the user what their project does -- even a rough description is
enough. Suggest a simple workflow relevant to their context (e.g., "classify a support ticket",
"score a lead", "send a notification on a Stripe event"). Name the domain in kebab-case.

**Step 2: Define schemas in `core/types/`.** Open `core/types/index.ts`. Walk the user through
the schema convention:

```typescript
export const myWorkflowInputSchema = z.object({
  // define fields here
})
export type MyWorkflowInput = z.infer<typeof myWorkflowInputSchema>

export const myWorkflowOutputSchema = z.object({
  // define fields here
})
export type MyWorkflowOutput = z.infer<typeof myWorkflowOutputSchema>
```

Explain: `z.infer` gives you a TypeScript type from the schema so the type system can check your
handlers. You define once, get both runtime validation and compile-time inference. The schema
lives in `core/` so the frontend can import it for form validation without importing SDK worker
code.

Explain common field types: `z.string()`, `z.number()`, `z.boolean()`, `z.enum([...])`,
`z.string().optional()`, `z.object({...})`, `z.array(z.string())`.

After writing the schemas, run:

```bash
pnpm -C core check-types
```

Fix any TypeScript errors before continuing.

**Step 3: Create the domain directory.** Create `operations/src/<domain>/` with two files:
`index.ts` (the domain barrel) and `<workflow-name>.ts` (the workflow implementation). Mirror
the echo structure but use the new schemas imported from `@core/types`.

Key points to emphasize:

- `resourceId` must be unique within the organization (kebab-case).
- `status: 'dev'` for new resources.
- `next: null` on the last step.
- Import schemas from `@core/types`, never define them inline in the workflow file.

**Step 4: Register in `operations/src/index.ts`.** Show the current registry:

```typescript
import * as example from './example/index.js'
import * as emailNotification from './email-notification/exports.js'

const org: DeploymentSpec = {
  version: '0.1.0',
  workflows: [...example.workflows, ...emailNotification.workflows],
  agents: [...example.agents, ...emailNotification.agents]
}
```

Add the new domain import and spread its `workflows` into the `DeploymentSpec`.

**Step 5: Validate.** Run `pnpm -C operations check`. The output should now show N+1 resources
with 0 errors.

**Verification:** `pnpm -C operations check` passes with the new workflow listed.

**Completion signal:** Mark `[ ] 5 Custom workflow with schemas` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 6: Platform tools and credentials

**Goal:** The user understands singleton adapters vs factory adapters, imports them correctly,
and knows how to create a credential in Command Center.

**Estimated time:** 20 min

**Files referenced:** `.claude/skills/elevasis/SKILL.md` (creds section),
`node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`

**Flow:**

Tell the user:

> "The platform provides typed adapters for external services: CRM, email, payments, AI, storage,
> and more. You call them from workflow handlers without putting API keys in your code."

**Singleton adapters.** Import once at the top of the worker file, use directly:

```typescript
import { llm, scheduler, storage, notifications } from '@elevasis/sdk/worker'
```

These are singletons -- no credential binding at construction. They use platform-managed config.
Use them for: LLM generation, scheduling, object storage, in-app notifications.

**Factory adapters.** Credential-bound: constructed with a credential name, then used:

```typescript
import { createAttioAdapter } from '@elevasis/sdk/worker'

const attio = createAttioAdapter('my-attio-cred')
// inside a step handler:
await attio.createRecord({ ... })
```

The string `'my-attio-cred'` is the credential name you register in Command Center. The naming
convention is `{org}-{service}` kebab-case, e.g. `acme-attio`, `acme-stripe`.

**Credential setup in Command Center.** Walk the user through: Command Center -> Settings ->
Credentials -> Create. The credential is stored encrypted on the platform. Workers access it at
runtime through the adapter. It never appears in code or `.env`. The `.env` file holds only
`ELEVASIS_PLATFORM_KEY` for CLI authentication.

**Common error.** `PlatformToolError: credential not found` means the credential name in code
does not exactly match what was created in Command Center. Check spelling and case; credential
names are case-sensitive.

**CLI credential management.** Show the `/elevasis creds` surface as an alternative to the UI:

```bash
pnpm elevasis-sdk creds list
pnpm elevasis-sdk creds create --name acme-attio --type api-key --value '{"apiKey":"..."}'
```

**Verification:** Ask: "Where do integration credentials live at runtime?" Answer: on the platform,
injected by the worker runtime. Not in `.env`, not in code.

**Completion signal:** Mark `[ ] 6 Platform tools and credentials` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 7: Multi-step and conditionals

**Goal:** The user implements a two-step LINEAR workflow and a CONDITIONAL workflow, understanding
how data flows between steps.

**Estimated time:** 25 min

**Files referenced:** `node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`,
`apps/docs/content/docs/sdk/sdk/concepts.mdx` (design decisions section)

**Flow:**

**Step 1: LINEAR steps.** Extend the workflow from Item 5 (or create a new one) to add a second
step. Show the `next` field on step 1 pointing to the key of step 2:

```typescript
steps: {
  fetchData: {
    id: 'fetchData',
    name: 'Fetch Data',
    handler: async (input, context) => {
      // ... fetch or compute something
      return { result: 'some-value' }
    },
    inputSchema: myInputSchema,
    outputSchema: z.object({ result: z.string() }),
    next: 'processData'       // <-- key of the next step
  },
  processData: {
    id: 'processData',
    name: 'Process Data',
    handler: async (input, context) => {
      const { result } = input  // receives output from fetchData
      return { finalResult: result.toUpperCase() }
    },
    inputSchema: z.object({ result: z.string() }),
    outputSchema: myOutputSchema,
    next: null
  }
},
entryPoint: 'fetchData'
```

Emphasize: the output schema of step N must match the input schema of step N+1. The platform
validates this chain at deploy time. Data passes from step to step -- no global state, no shared
variables.

**Step 2: CONDITIONAL routing.** Import `StepType` and show a conditional branch:

```typescript
import type { WorkflowDefinition, StepType } from '@elevasis/sdk'

// ... inside steps:
routeStep: {
  id: 'routeStep',
  type: StepType.CONDITIONAL,
  name: 'Route by Score',
  handler: async (input, context) => {
    if (input.score >= 80) return { path: 'approve', ...input }
    if (input.score >= 50) return { path: 'review', ...input }
    return { path: 'reject', ...input }
  },
  routes: {
    approve: 'approveStep',
    review: 'reviewStep',
    default: 'rejectStep'   // always include a default
  },
  // ...
}
```

Always include a `default` route. Without it, an execution whose output does not match any
explicit key will fail at runtime.

**Verification:** `pnpm -C operations check` passes with the multi-step workflow registered. Ask
the user what happens if they omit the `default` route (runtime failure for unmatched paths).

**Completion signal:** Mark `[ ] 7 Multi-step and conditionals` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 8: Going to production

**Goal:** The user knows the dev/prod split, version bumping flags, and the SDK CLI CWD
guidance in the deployment rule.

**Estimated time:** 15 min

**Files referenced:** `.claude/rules/deployment.md`

**Commands:** `pnpm -C operations deploy:prod`

**Flow:**

**Dev vs prod targeting.** The default `pnpm -C operations deploy` targets the development
environment. To deploy to production:

```bash
pnpm -C operations deploy:prod
```

In `config.status`: `'dev'` means the resource is visible only in dev mode. Change to `'prod'`
before deploying to production -- otherwise the resource deploys but is not exposed in the
production surface.

**Version bumping.** The deploy command accepts version bump flags that are written back to
`operations/src/index.ts`:

```bash
pnpm -C operations deploy:prod   # no bump (same version)
# or pass a flag via pnpm scripts if wired in package.json:
pnpm elevasis-sdk deploy --prod --patch   # 1.0.0 -> 1.0.1
pnpm elevasis-sdk deploy --prod --minor   # 1.0.0 -> 1.1.0
pnpm elevasis-sdk deploy --prod --major   # 1.0.0 -> 2.0.0
```

Bump rules: `--patch` for bug fixes, `--minor` for new features (no breaking schema changes),
`--major` for breaking input/output schema changes.

**SDK CLI CWD guidance.** Read `.claude/rules/deployment.md`. Two critical rules:

1. The `--prod` flag belongs to the `deploy` command:

   ```bash
   # Correct
   pnpm elevasis-sdk deploy --prod
   ```

   The `pnpm -C operations deploy:prod` script in `package.json` handles this correctly.
   Prefer the script over a raw CLI call for production deploys.

2. Never use bare `cd <dir> && elevasis-sdk ...` -- use a subshell `(cd <dir> && ...)` or
   `pnpm -C <dir> ...` to avoid CWD drift that causes env resolution failures.

**Standard pre-production checklist.** Walk the user through:

```
1. pnpm -C operations check        -- validate resources
2. pnpm -C operations check-types  -- TypeScript type-check
3. pnpm elevasis-sdk exec <id> -i '...'   -- test in dev
4. pnpm -C operations deploy:prod  -- ship to prod
```

**Verification:** The user can explain where `--prod` must be placed and why.

**Completion signal:** Mark `[ ] 8 Going to production` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

## SECTION C -- The Organization Model

### Item 9: /om ceremony -- identity, customers, offerings via the layered flow

**Goal:** The user runs `/om` and understands the six-step write ceremony, why direct
edits to `core/config/organization-model.ts` are blocked, and the layered flow order.

**Estimated time:** 25 min

**Files referenced:** `.claude/skills/om/SKILL.md`, `core/config/organization-model.ts`,
`.claude/rules/organization-os.md`

**Flow:**

Tell the user:

> "The organization model is the semantic contract layer that defines your business reality:
> who you are, who your customers are, what you offer, your team, and your goals. All edits go
> through `/om` -- not direct file edits."

**Why direct edits are blocked.** Read `core/config/organization-model.ts`. The file calls
`resolveOrganizationModel()` which runs Zod cross-reference validation: every customer segment
referenced in offerings must exist in `customers`, every resource mapping must reference a valid
system ID, and so on. A direct edit that passes TypeScript can still fail the cross-ref check,
leaving the project in a broken state. The `/om` ceremony runs both checks and rolls back
on failure.

**The six-step ceremony.** Walk through `.claude/skills/om/SKILL.md`:

1. **Snapshot** -- reads `core/config/organization-model.ts` into memory before any edit
2. **Propose** -- shows the diff and the proposed new value alongside current value
3. **Confirm** -- pauses for explicit user confirmation; never writes without a clear yes
4. **Write** -- applies the edit
5. **Validate** -- runs `pnpm -C operations check-types` (TypeScript) then `pnpm -C operations
check` (Zod parse via `resolveOrganizationModel()`)
6. **Rollback** -- if either check fails, restores from the Step 1 snapshot

**The layered flow.** Run `/om` with no argument to walk through the full flow:
identity -> customers -> offerings -> roles -> goals -> techStack. The agent reads the current
state of each domain, proposes any missing or incomplete fields, and asks for confirmation before
writing.

Or target a specific domain:

- `/om identity` -- legal name, mission/vision, industry, geography, timezone
- `/om customers` -- customer segments with jobs-to-be-done, pains, gains
- `/om offerings` -- products and services with pricing model and segment references
- `/om roles` -- role chart with responsibilities and reporting lines
- `/om goals` -- organizational goals with period and measurable outcomes
- `/om techStack` -- external SaaS and integration context tied to OM Resources descriptors or knowledge nodes

**Demo.** Ask the user to run `/om identity`. Walk through the ceremony together -- read
the current state, propose an edit, confirm, watch it validate.

**Verification:** The user can describe what `resolveOrganizationModel()` does and why the
rollback step exists.

**Completion signal:** Mark `[ ] 9 /om ceremony` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 10: Systems, actions, and labels

**Goal:** The user can enable/disable a System via `/om systems`, understand Actions as
invokable operations, and rename a display label via `/om labels`.

**Estimated time:** 15 min

**Files referenced:** `.claude/skills/om/operations/features.md` (legacy compatibility),
`.claude/skills/om/operations/labels.md`, `core/config/organization-model.ts`

**Flow:**

**Systems.** The org model uses Systems for availability, routing, ownership, navigation
grouping, and knowledge mounts. Toggle availability by running:

```
/om systems
```

The ceremony proposes the change (e.g., `seo: false -> true`), asks for confirmation, writes,
and validates. Older scaffold recipes and UI packages may still say "feature"; translate that to
System when discussing Organization OS availability or routing.

System toggles affect the UI shell: a disabled System removes its nav entry and routes from
the shell. Actions are the invokable operations owned or exposed by a System.

**Labels.** The `labels` domain controls display strings on enum entries -- CRM pipeline stages,
lead-gen lifecycle statuses, project statuses, and so on. Rename by running:

```
/om labels
```

The agent reads the current enum entries (each carries an `id`, `label`, and `semanticClass`
field), proposes a label rename, and writes through the ceremony. The `semanticClass` field
should not change -- it maps to semantic behavior like `blocked`, `in_progress`, `complete`.

Show an example: renaming the `discovery` CRM stage to `qualification`.

**Verification:** Ask: "What is the difference between a System toggle and a label rename?"
(Toggle controls visibility/routing. Label rename changes display text on an existing enum
entry without changing its semantic ID or behavior.)

**Completion signal:** Mark `[ ] 10 Systems, actions, and labels` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 11: Entity extensions -- BaseProject, BaseDeal

**Goal:** The user reads the entity extension pattern in `core/types/entities.ts` and understands
how to add project-specific metadata fields to base entities.

**Estimated time:** 20 min

**Files referenced:** `core/types/entities.ts`, `core/config/organization-model.ts`
(for the OM Resources descriptor context)

**Flow:**

**Read the demo extension.** Open `core/types/entities.ts`. Walk through both patterns it
demonstrates:

**Pattern 1: extend with metadata.**

```typescript
import { BaseProjectSchema, type BaseProject } from '@elevasis/core/entities'

export const ProjectMetaSchema = z.object({
  budget: z.number().nonnegative(),
  clientPriority: z.enum(['low', 'medium', 'high'])
})
export type ProjectMeta = z.infer<typeof ProjectMetaSchema>

export const ProjectSchema = BaseProjectSchema.extend({ metadata: ProjectMetaSchema })
export type Project = BaseProject<ProjectMeta>
```

The base types from `@elevasis/core/entities` are generic over a `\<TMeta>` slot. You pass your
metadata type to specialize the base. `BaseProjectSchema.extend(...)` is the Zod idiom; the
TypeScript type is `BaseProject<ProjectMeta>`.

**Pattern 2: use a base entity unchanged.**

```typescript
export const DealSchema = BaseDealSchema
export type Deal = BaseDeal
```

When you have no project-specific fields, re-export the base. This keeps the import path
consistent (`@core/types` everywhere) while avoiding redundant extension.

**Using entity types in workflows.** When authoring a workflow that receives or returns a
Project or Deal, reference these types in the input/output schemas rather than re-declaring the
shape. This ensures the workflow contract stays in sync with the entity definition as it evolves.

**Reference for all base shapes.** `@elevasis/core/entities` exports: `BaseProject`,
`BaseProjectSchema`, `BaseProjectInput`, `BaseMilestone`, `BaseMilestoneSchema`,
`BaseTask`, `BaseTaskSchema`, `BaseDeal`, `BaseDealSchema`, `BaseCompany`, `BaseCompanySchema`,
`BaseContact`, `BaseContactSchema`.

**Domain rename note.** In `core/config/organization-model.ts`, the domain config fields were
renamed in the 2026-04-20 expansion: `crm` -> `sales`, `leadGen` -> `prospecting`,
`delivery` -> `projects`. Older feature ID constants may still appear in UI package code and
scaffold recipes; treat them as implementation constants, not live Organization OS primitives.

**Verification:** The user can explain when to use Pattern 1 vs Pattern 2 and name two base
entity types they could extend.

**Completion signal:** Mark `[ ] 11 Entity extensions` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

## SECTION D -- Modules (load on demand)

Modules in Section D are available any time. No Section B or C prerequisites are enforced. After
completing a module, redisplay the full menu with updated progress markers.

---

### Item 12: HITL

**Goal:** The user implements an approval gate using `approval.create()` and understands the
full lifecycle through Command Queue.

**Estimated time:** 25 min

**Files referenced:** `.claude/rules/error-handling.md`,
`node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`

**Flow:**

**What HITL is.** Human-in-the-Loop (HITL) pauses a workflow execution and waits for a human
decision before continuing. The execution is suspended at the approval gate; the platform stores
the pending state. When a user approves or rejects in Command Center, the execution resumes.

**Implementation.** Import `approval` from `@elevasis/sdk/worker` and call it inside a step
handler:

```typescript
import { approval } from '@elevasis/sdk/worker'

const decision = await approval.create({
  title: 'Approve Invoice',
  description: `Invoice #${input.invoiceId} for $${input.amount} ready for approval.`,
  context: { invoiceId: input.invoiceId, amount: input.amount }
})

if (decision.approved) {
  // continue the workflow
} else {
  // handle rejection
}
```

**Lifecycle.** The step calls `approval.create()` -> platform suspends the execution -> a
pending item appears in the Command Queue in Command Center -> user reviews and approves or
rejects -> execution resumes from the same step with `decision.approved` set.

**Variant rule.** Always use `variant: 'light'` (hardcoded by the platform; never pass `variant`
in the `approval.create()` call -- the field does not exist on the call signature and will cause
a TypeScript error).

**Verify.** Deploy the workflow, execute it, confirm that it appears as pending in Command Center
Command Queue, approve it, and confirm the execution completes.

**Completion signal:** Mark `[ ] 12 HITL` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 13: Schedules

**Goal:** The user understands the three Task Scheduler types (Recurring, Relative, Absolute),
uses the `scheduler` singleton inside a workflow, and knows the cron syntax the platform accepts.

**Estimated time:** 20 min

**Files referenced:** `node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`

**Flow:**

**The `scheduler` singleton.** Import from `@elevasis/sdk/worker` to trigger workflow actions
from within a running step:

```typescript
import { scheduler } from '@elevasis/sdk/worker'

await scheduler.schedule({
  resourceId: 'my-workflow',
  triggerAt: new Date(Date.now() + 60 * 60 * 1000), // 1 hour from now
  input: { ... }
})
```

**Task Scheduler UI -- three types.** Walk the user through Command Center -> Task Scheduler:

- **Recurring** -- cron-based, runs on a schedule. Standard cron syntax: `0 9 * * 1-5` (9am
  weekdays). The platform interprets cron in UTC.
- **Relative** -- fires N minutes/hours/days after a trigger event (e.g., "24 hours after a
  deal enters a stage").
- **Absolute** -- fires at a specific UTC datetime (one-shot, not recurring).

**Creating a schedule.** Show the Task Scheduler create flow: select resource, choose type,
configure the trigger, set input. Schedules appear as `workflow_schedules` rows in the DB -- the
`elevasis-sdk executions` command shows executions triggered by schedules alongside manual ones.

**Verification:** Ask: "What time zone does the platform use for cron expressions?" (UTC.)

**Completion signal:** Mark `[ ] 13 Schedules` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 14: Notifications + integrations

**Goal:** The user uses the `notifications` and `email` singletons and wires a real integration
adapter using a credential from the project's identity.

**Estimated time:** 25 min

**Files referenced:** `.claude/skills/elevasis/SKILL.md` (credentials section),
`node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`

**Flow:**

**`notifications` singleton.** Sends in-app notifications visible in Command Center:

```typescript
import { notifications } from '@elevasis/sdk/worker'

await notifications.send({
  title: 'Campaign complete',
  message: `Processed ${count} leads.`,
  level: 'info'  // 'info' | 'warning' | 'error'
})
```

**`email` singleton.** Sends transactional email through the platform's configured mail provider:

```typescript
import { email } from '@elevasis/sdk/worker'

await email.send({
  to: 'user@example.com',
  subject: 'Your report is ready',
  body: 'Here is a summary...'
})
```

**Real adapter integration.** Read `core/config/organization-model.ts` and find the relevant
OM Resources descriptor or project knowledge node for the integration. Use the documented
credential name as the constructor argument:

```typescript
import { createAttioAdapter } from '@elevasis/sdk/worker'
const attio = createAttioAdapter('acme-attio')
```

Walk the user through the end-to-end: create the credential in Command Center, reference it in
the adapter constructor, deploy, execute a test run, check that the integration call succeeds.

**Common error.** If the credential is missing or misspelled, the adapter throws
`PlatformToolError: credential not found`. Check the exact name in Command Center.

**Verification:** A test execution that calls the integration adapter completes without a
`PlatformToolError`.

**Completion signal:** Mark `[ ] 14 Notifications + integrations` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 15: Error handling

**Goal:** The user knows the three error types, uses `context.logger`, and implements a
try/catch with retryable vs permanent error distinction.

**Estimated time:** 20 min

**Files referenced:** `.claude/rules/error-handling.md`,
`node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`

**Flow:**

Read `.claude/rules/error-handling.md` before starting. Then walk through each error type:

**`ExecutionError`.** Base class for workflow/step failures. Throw to halt an execution with a
user-readable message:

```typescript
import { ExecutionError } from '@elevasis/sdk'

throw new ExecutionError('Payment processing failed', {
  context: { invoiceId: input.invoiceId }
})
```

**`PlatformToolError`.** Thrown by `platform.call()` and typed adapters when an integration
call fails. Always check `err.retryable`:

```typescript
import { PlatformToolError } from '@elevasis/sdk'

try {
  await attio.createRecord({ ... })
} catch (err) {
  if (err instanceof PlatformToolError) {
    if (err.retryable) {
      context.logger.warn(`Transient error (rate limit or timeout): ${err.message}`)
      // safe to retry or re-throw for the caller to handle
    } else {
      throw new ExecutionError(`Permanent integration error: ${err.message}`)
    }
  }
  throw err
}
```

**`ToolingError`.** Internal SDK infrastructure error. Treat as non-retryable.

**`context.logger`.** Use it for all logging inside handlers. `console.log` is not captured by
the platform worker runtime. Log levels: `debug`, `info`, `warn`, `error`. The error message in
an unhandled throw surfaces directly to CLI output and Command Center -- write it for humans.

**Common retryable codes.** `rate_limit_exceeded` and `timeout_error` are retryable.
`credentials_invalid` and `validation_error` are not.

**Verification:** Demonstrate a handler that catches a `PlatformToolError`, checks `retryable`,
logs appropriately, and re-throws as an `ExecutionError` with a clear message.

**Completion signal:** Mark `[ ] 15 Error handling` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 16: LLM and agents

**Goal:** The user calls `llm.generate()` with structured output (Zod -> JSON Schema -> typed
result) and understands when to use an agent definition vs a workflow definition.

**Estimated time:** 25 min

**Files referenced:** `node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`,
`apps/docs/content/docs/sdk/sdk/concepts.mdx` (workflow vs agent section)

**Flow:**

**`llm.generate()` with structured output.** The `llm` singleton from `@elevasis/sdk/worker`
accepts a Zod output schema and returns a typed result:

```typescript
import { llm } from '@elevasis/sdk/worker'
import { z } from 'zod'

const classificationSchema = z.object({
  category: z.enum(['support', 'billing', 'feature-request', 'other']),
  confidence: z.number().min(0).max(1),
  summary: z.string()
})

const result = await llm.generate({
  prompt: `Classify this ticket: ${input.ticketBody}`,
  outputSchema: classificationSchema,
  model: 'gpt-4o',
  temperature: 0.2
})

// result is typed as z.infer<typeof classificationSchema>
context.logger.info(`Classified as: ${result.category} (${result.confidence})`)
```

The SDK converts the Zod schema to JSON Schema internally. The LLM is instructed to return JSON
matching that shape. The result is validated and returned as the inferred TypeScript type.

**Agent definition vs workflow definition.** Read the glossary entries from
`apps/docs/content/docs/sdk/sdk/concepts.mdx`:

- **Workflow** -- deterministic, step-by-step, 300s timeout. Use when inputs and outputs are
  known and the process is predefined.
- **Agent** -- autonomous, tool-calling, 600s timeout (configurable). Use when the agent needs
  to make decisions, call tools iteratively, or navigate ambiguous inputs.

When to use an agent: the task requires the LLM to decide WHICH tools to call and in WHAT order
based on what it finds (not predetermined routing). When to use a workflow: the sequence of steps
is predetermined, even if LLM judgment is involved inside individual steps.

**Model selection.** `gpt-4o` for complex reasoning, `gpt-4o-mini` for speed-sensitive tasks,
`o3-mini` for multi-step reasoning chains. Temperature: `0.0` for deterministic classification,
`0.2-0.4` for structured extraction, `0.7+` for creative generation.

**Verification:** A workflow step calls `llm.generate()` with a Zod schema and returns a typed
value. The type system confirms the result matches the schema without a cast.

**Completion signal:** Mark `[ ] 16 LLM and agents` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 17: Composition (execution.trigger)

**Goal:** The user chains two workflows using `execution.trigger()`, declares the relationship in
`DeploymentSpec`, and can find the edge in Command View.

**Estimated time:** 20 min

**Files referenced:** `operations/src/index.ts`,
`node_modules/@elevasis/sdk/reference/scaffold/operations/workflow-recipes.md`

**Flow:**

**`execution.trigger()`.** Call from inside a step handler to launch another workflow
asynchronously:

```typescript
import { execution } from '@elevasis/sdk/worker'

const { executionId } = await execution.trigger({
  resourceId: 'downstream-workflow',
  input: { ...outputFromCurrentStep }
})

context.logger.info(`Triggered downstream-workflow: ${executionId}`)
```

The calling step does not wait for the downstream workflow to complete. If you need the result,
use async polling or chain via HITL.

**Declare the relationship in `DeploymentSpec`.** In `operations/src/index.ts`, add a
`relationships` entry so the platform knows this workflow triggers another:

```typescript
const org: DeploymentSpec = {
  version: '0.1.0',
  workflows: [...],
  agents: [...],
  relationships: [
    {
      source: 'upstream-workflow',
      target: 'downstream-workflow',
      type: 'triggers'
    }
  ]
}
```

This metadata populates the Command View graph. Without it, the edge is not visible in the UI
even if the trigger call works.

**Command View.** Show the user how to find Command View in Command Center -> Operations.
Deployed relationships appear as directed edges between resource nodes. This is the primary
debugging surface for understanding how automations are connected.

**Verification:** Deploy both workflows with the relationship declared, then view the edge in
Command View.

**Completion signal:** Mark `[ ] 17 Composition` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

## SECTION E -- Power User

### Item 18: Rules, memory, scaffold registry

**Goal:** The user can navigate the `.claude/rules/` and `.claude/memory/` directories and
understands the scaffold registry's purpose.

**Estimated time:** 20 min

**Files referenced:** `.claude/rules/` (all files), `.claude/memory/` (layout),
`.claude/registries/scaffold-registry.yml` (reference, monorepo-side concept)

**Flow:**

**Rules directory.** List the current rule files in `.claude/rules/`. Each rule is automatically
loaded by the agent on every session based on its `paths:` frontmatter (path globs that trigger
auto-load). Key files:

- `agent-start-here.md` -- always-loaded canonical first-read; task-class routing and boundary
  resolution
- `vibe.md` -- always-loaded ambient intent classifier
- `organization-os.md` -- loaded when touching org model, System access, Action routing, or entity work
- `deployment.md` -- loaded when deploying resources
- `error-handling.md` -- loaded when authoring handlers with error concerns
- `execution.md` -- loaded when reasoning about the runtime model
- `observability.md` -- loaded when using `context.logger` or inspecting executions
- `operations.md` -- loaded when touching `operations/src/`
- `platform.md` -- loaded when importing from `@elevasis/sdk`
- `shared-types.md` -- loaded when touching `core/types/`
- `task-tracking.md` -- loaded when managing project tasks
- `active-change-index.md` -- flags areas under active architecture change; trust this over
  stable scaffold docs for those areas

To add a new rule: create `.claude/rules/<domain>.md` with `paths:` frontmatter matching the
glob of files it applies to. The rules system discovers it automatically -- no registration step.

**Memory directory.** List `.claude/memory/`. Common files:

- `profile.md` -- track choice and tone implications (written by this tutorial)
- `tutorial-progress.md` -- per-item progress markers for both tracks

Memory files are readable by the agent at session start. The `profile.md` file is particularly
important: it sets the project-wide tone for all sessions after the tutorial track is selected.

**Scaffold registry (monorepo-side concept).** `.claude/registries/scaffold-registry.yml` is the
monorepo's source-of-truth for scaffold dependencies -- it maps source paths to generated or
manual scaffolds that depend on them. It drives the PostToolUse reminder hook and `/work handoff`
preflight in the monorepo. In an external project you are a consumer, not a contributor, of that
registry. The relevant thing to know: when the SDK ships a template update, the registry controls
which files are Tier 1 (always replaced on sync), Tier 2 (merge-aware), and Tier 3 (never
touched). Item 19 covers this in detail.

**Verification:** Ask: "How does the agent know to load `deployment.md` when you run a deploy?"
(Answer: the `paths:` frontmatter in the rule file matches `operations/` file globs and deploy
command patterns.)

**Completion signal:** Mark `[ ] 18 Rules, memory, scaffold registry` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

### Item 19: Template lifecycle and /git-sync

**Goal:** The user understands the three-tier file classification, runs `/git-sync`, and knows
what manual reconciliation is required after a template update.

**Estimated time:** 20 min

**Files referenced:** `.claude/skills/git-sync/SKILL.md`, `.claude/sync-notes/` (directory)

**Flow:**

Tell the user:

> "The template you scaffolded from is versioned. When a new SDK version ships, the template
> updates. `/git-sync` pulls those changes and tells you what changed. You decide what to do
> with them."

**Three-tier file classification.** Walk through the tiers:

- **Tier 1 (always replaced)** -- SDK-owned files that ship in a known correct state:
  `.claude/skills/*/SKILL.md` (except project-authored skills), `.claude/hooks/`, `CLAUDE.md`
  sections that are platform-managed. These are overwritten on every template sync. Do not put
  custom logic in them; instead use Tier 3 files for project-specific config.
- **Tier 2 (merge-aware)** -- Files where the template ships defaults but the project may have
  customized: `core/config/organization-model.ts`, `.claude/rules/active-change-index.md`,
  vibe classifier threshold. The sync does not blindly overwrite -- the agent surfaces the diff
  and the user decides what to keep.
- **Tier 3 (never touched)** -- Project-owned files the template never overwrites: project
  source code in `operations/src/`, `ui/src/`, all `core/types/` files, `core/config/extensions/`,
  custom rules added to `.claude/rules/`, everything in `.claude/memory/`.

**Running `/git-sync`.** Read `.claude/skills/git-sync/SKILL.md` and walk the user through each
step:

1. Check for uncommitted changes (`git status --short`). Stop if dirty.
2. Snapshot current sync-note filenames before pulling.
3. `git pull --rebase` -- stop and report conflicts; do not auto-resolve.
4. Detect dependency baseline changes; run `pnpm install` if `package.json` or
   `pnpm-lock.yaml` changed.
5. Run baseline verification: `pnpm -C ui check-types`, `pnpm -C ui build`,
   `pnpm -C operations check`, `pnpm -C operations check-types`.
6. Surface newly introduced sync notes from `.claude/sync-notes/`. Each note has required
   actions and a verification section.
7. Stop. `/git-sync` does not auto-reconcile. Manual follow-up is explicit.

**Sync notes contract.** Files in `.claude/sync-notes/` named `YYYY-MM-DD-<slug>.md` are
operative release guidance. They use fixed headings: `## Why this note exists`,
`## Applies to`, `## Required actions`, `## Verification`, `## Not handled by /git-sync`.
Never edit operative note filenames after they ship -- they are append-only.

**Verification:** The user knows which of their own files are Tier 3 (never touched) and can
explain why `/git-sync` stops before reconciliation rather than auto-applying diffs.

**Completion signal:** Mark `[ ] 19 Template lifecycle and /git-sync` as `[x] YYYY-MM-DD` in
`.claude/memory/tutorial-progress.md`.

---

## After All 19 Items Complete

When all 19 items are marked `[x]` in `.claude/memory/tutorial-progress.md`, deliver the
following closing script:

> "You've completed the technical track. Here's where to go from here:
>
> - **Build real automations.** You now have the full surface: workflows, schemas, HITL, schedules,
>   LLM integration, error handling, and composition. Start with the domain most relevant to your
>   project and use `/project create` to track the work.
> - **SDK reference scaffold.** `node_modules/@elevasis/sdk/reference/scaffold/index.mdx` is the
>   canonical recipe index for anything you want to build: adding a System or UI feature, extending lead-gen,
>   customizing CRM actions, authoring agents. Read it whenever you are starting something new.
> - **Explore the codebase.** `/explore` is your tool for open-ended questions about how things
>   are wired. Use it before making changes in unfamiliar parts of the project.
> - **Keep the org model current.** Run `/om` any time your business reality changes --
>   new offerings, new customer segments, new goals. The model is the agent's vocabulary for
>   your project; an accurate model means better agent output.
> - **Stay current with template updates.** When a new SDK version ships, run `/git-sync` to pull
>   it and read the sync notes. They tell you exactly what changed and what manual follow-up is
>   needed."
