Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions .changeset/21320-agent-lifecycle-retired.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
'@objectstack/spec': minor
'@objectstack/platform-objects': patch
---

feat(spec)!: retire `agent.lifecycle`, the agent conversation state machine, and with it the XState `StateMachineSchema` family — a conversation phase is a skill with `triggerConditions`, orchestration is Flow, record transitions are the `state_machine` validation rule (#21320)

**BREAKING** — `agent.lifecycle` was parsed and never read. No runtime, in this
repository or in the cloud AI runtime that executes agents, moved an agent through a
declared state or refused an undeclared transition, so an authored machine changed
nothing an agent did (ADR-0049 enforce-or-remove). Enforcing it would have meant a
statechart interpreter beside Flow, the two-engine shape ADR-0020 rejected. Authoring
now refuses the key by name, with a prescription, and TypeScript rejects it.

Its value schema had no other authorable door: ADR-0020 had already retired the XState
shape as a record-lifecycle declaration and kept the file only for this key. So the
family leaves the package with it.

### FROM → TO

| before | what to write instead |
| --- | --- |
| `agent.lifecycle` — any value | delete the key. |
| a conversation phase in the machine (its own instructions and tools) | a skill with its own `instructions` and `tools`, selected by its `triggerConditions`, listed in the agent's `skills`. |
| a multi-step process in the machine | a Flow. |
| a record's status transitions in the machine | a `state_machine` validation rule in the object's `validations`: `{ type: 'state_machine', field, transitions: { from: [to, …] } }`. |
| `StateMachineSchema`, `StateNodeSchema`, `TransitionSchema`, `ActionRefSchema`, `GuardRefSchema` and the types `StateMachineConfig`, `StateNode`, `StateNodeConfig`, `Transition`, `ActionRef`, `GuardRef` from `@objectstack/spec/automation` | no replacement: declare the shape your code needs itself, or drop it. For record transitions, `StateMachineValidationSchema` in `@objectstack/spec/data` is the enforced shape. |
| `StateNodeConfig` from `@objectstack/spec` or `@objectstack/spec/ai` | removed with the family; nothing in those entries mentions it any more. |

**The one-line fix: delete `lifecycle`; put phase-scoped instructions and tools in
skills with `triggerConditions`, and orchestration in Flow.** `os migrate meta --from 17`
lists the mechanical edits for existing sources (the `lifecycle` deletion). Where each
deleted machine's intent goes is the author's judgement.

The refusal is a parse error at `lifecycle` naming the key and the fix, and the key
fails `tsc` (its input type is `never`).

### The retirement kit

- **Tombstone.** `lifecycle` is a `retiredKey()` on `AgentSchema` carrying the
prescription; the agent metadata form no longer offers it.
- **D2 conversion `agent-lifecycle-removed`** (step 18, retired from the load path):
it deletes `lifecycle` from every agent, whatever it holds. The delete is lossless,
because no value of it ever changed what an agent did. Stored `sys_metadata` agent
rows and built artifacts replay it; one notice per agent. An object's ADR-0057
`lifecycle` block shares the name and is not touched.
- **D3 entry `agent-lifecycle-retired`** carries the judgement the conversion cannot
make: which of the three destinations each deleted machine meant.
- **`RETIRED_KEYS_BY_MAJOR[18]`** registers `ai/Agent:lifecycle`, and
**`RETIRED_DEFS_BY_MAJOR[18]`** registers the five published defs
`automation/StateMachine`, `automation/StateNode`, `automation/Transition`,
`automation/ActionRef` and `automation/GuardRef`. Their reference page
(`references/automation/state-machine`) is gone.
- **No deprecation window**, per the project's startup-stage posture.

### The liveness ledger

The `agent.lifecycle` row moves `experimental` → `dead` with a REMOVED note
(`verifiedAt` 2026-10-02); the tombstone keeps it in the walked shape. No `agent` row is
`experimental` any more. `os validate` and every other parsing door refuse the key at
parse, before any advisory runs. `os lint` reads the unparsed stack, so it now grades the
key `liveness-dead-property` where it used to say `liveness-experimental-property`.

### `@objectstack/platform-objects`

The agent metadata-form catalogs drop the `lifecycle` row's label and help text in all
four locales.

⚠️ **The out-of-repo consumer population is NOT MEASURED.** `@objectstack/spec` is
published: tenant-authored agents, and code outside this repository importing the
family's exports, were not measured. This repository authors no `agent.lifecycle`
outside `packages/spec` and imports none of the family outside it; the pinned objectui
checkout imports none of the family and reads no `agent.lifecycle`.

Clause-②: yes (narrowing)

<!-- adr-0087: registered agent-lifecycle-removed, agent-lifecycle-retired -->
69 changes: 39 additions & 30 deletions content/docs/automation/workflows.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ObjectStack no longer has a standalone Salesforce-style Workflow Rule authoring
type. Use:

- **Flow** for event-triggered or scheduled automation.
- **State machine metadata** for strict lifecycle transitions.
- **A `state_machine` validation rule** for strict lifecycle transitions.
- **Approval nodes** inside Flow for human approval pauses.

This page keeps the historical route but documents the current split.
Expand Down Expand Up @@ -60,42 +60,51 @@ registration/runtime.

## State machines for lifecycle constraints

Use `StateMachineSchema` when the core requirement is "this object can only move
through these states by these events."
Use a `state_machine` validation rule when the core requirement is "this record
can only move through these states." It is one of the object's `validations`: a
flat table of each state's allowed next states, enforced by the write path (see
[State Machine](/docs/protocol/objectql/state-machine)).

{/* os:check */}
```typescript
import type { StateMachineConfig } from '@objectstack/spec/automation';

export const caseLifecycle: StateMachineConfig = {
id: 'case_lifecycle',
initial: 'new',
states: {
new: {
on: {
ASSIGN: { target: 'assigned' },
},
},
assigned: {
on: {
RESOLVE: { target: 'resolved', cond: 'has_resolution' },
ESCALATE: { target: 'escalated' },
},
},
escalated: {
on: {
RESOLVE: { target: 'resolved', cond: 'has_resolution' },
import { ObjectSchema, Field } from '@objectstack/spec/data';

export const SupportCase = ObjectSchema.create({
name: 'support_case',
label: 'Support Case',
sharingModel: 'private',
fields: {
status: Field.select({
label: 'Status',
required: true,
options: [
{ label: 'New', value: 'new' },
{ label: 'Assigned', value: 'assigned' },
{ label: 'Escalated', value: 'escalated' },
{ label: 'Resolved', value: 'resolved' },
],
}),
},
validations: [
{
type: 'state_machine',
name: 'case_status_flow',
field: 'status',
events: ['update'],
message: 'Invalid case status transition.',
transitions: {
new: ['assigned'],
assigned: ['resolved', 'escalated'],
escalated: ['resolved'],
resolved: [],
},
},
resolved: {
type: 'final',
},
},
};
],
});
```

State machines describe valid transitions and guards. Use Flow nodes for side
effects around those transitions when you need notifications, record updates, or
The rule declares which transitions are legal. Use Flow nodes for side effects
around those transitions when you need notifications, record updates, or
external calls.

---
Expand Down
5 changes: 2 additions & 3 deletions content/docs/getting-started/quick-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -151,15 +151,14 @@ REST endpoints, real-time subscriptions, and discovery.
| **[Metadata](/docs/references/api/metadata)** | `metadata.zod.ts` | Metadata | API metadata endpoints |
| **[Storage](/docs/references/api/storage)** | `storage.zod.ts` | Storage | API storage operations |

## Automation Protocol (4 of 14 schemas)
## Automation Protocol (3 of 13 schemas)

Flows, state machines, approvals, and integrations.
Flows, approvals, and integrations. A record's lifecycle transitions are a `state_machine` validation rule on the object (see [State Machine](/docs/protocol/objectql/state-machine)).

| Protocol | Source File | Key Schemas | Purpose |
|:---------|:-----------|:------------|:--------|
| **[Flow](/docs/references/automation/flow)** | `flow.zod.ts` | Flow, FlowNode | Visual workflow builder |
| **[Approval](/docs/references/automation/approval)** | `approval.zod.ts` | ApprovalNodeConfig | Flow approval-node config |
| **[State Machine](/docs/references/automation/state-machine)** | `state-machine.zod.ts` | StateMachine | State machine definitions |
| **[Webhook](/docs/references/automation/webhook)** | `webhook.zod.ts` | Webhook | Outbound webhooks |

## Security Protocol (3 of 5 schemas)
Expand Down
13 changes: 1 addition & 12 deletions content/docs/references/ai/agent.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ const result = AIModelConfigSchema.parse(data);
| **role** | `string` | ✅ | The persona/role (e.g. "Senior Support Engineer") |
| **instructions** | `string` | ✅ | System Prompt / Prime Directives |
| **model** | `{ provider: Enum<'openai' \| 'azure_openai' \| 'anthropic' \| 'local'>; model: string; temperature: number; maxTokens?: number; … }` | optional | |
| **lifecycle** | `{ id: string; description?: string; contextSchema?: Record<string, any>; initial: string; … }` | optional | [EXPERIMENTAL — not enforced] State machine defining the agent conversation flow and constraints. Parsed but no runtime consumer yet. |
| **lifecycle** | `never` | optional | [REMOVED] `agent.lifecycle` was removed in @objectstack/spec 17.7.0 (ADR-0049 enforce-or-remove) — no runtime ever read it: no agent moved through a declared state and no transition was ever refused. Delete the key. A phase of a conversation is a skill with its own `instructions` and `tools`, selected by its `triggerConditions` (ADR-0064); multi-step process orchestration is a Flow (ADR-0019); a record's status transitions are a `state_machine` validation rule on the object (ADR-0020). Run `os migrate meta --from 17` to list the mechanical edits for existing sources; apply them by hand. |
| **surface** | `Enum<'ask' \| 'build'>` | optional (default: `"ask"`) | Product surface this agent binds ('ask' \| 'build') — ADR-0063 §1 |
| **skills** | `string[]` | optional | Skill names to attach (Agent→Skill→Tool architecture) |
| **tools** | `never` | optional | [REMOVED] `agent.tools` was removed in @objectstack/spec 17 — use `skills`. An agent reaches exactly the tools its surface-compatible skills declare (ADR-0064), so move each reference into a skill: a platform tool by its registered name, or `action_<name>` for one of your own AI-exposed Actions. This is NOT a rename — there is no key the value moves to: the migration DELETES the key and emits a notice naming each tool that was listed, and you re-declare each one in a skill by hand. ADR-0064 itself still reads `Proposed` and is cloud-owned — that scopes its RUNTIME half (tool resolution, which lives in cloud `service-ai`), not this rejection: the authoring invariant binds you here, and ADR-0109 (Accepted — implemented) is the in-repo record that carries it. Run `os migrate meta --from 16` to list the mechanical edits for existing sources; apply them by hand. |
Expand Down Expand Up @@ -80,17 +80,6 @@ const result = AIModelConfigSchema.parse(data);
| **maxTokens** | `number` | optional | |
| **topP** | `number` | optional | |

### Nested Shape: `Agent.lifecycle`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **id** | `string` | ✅ | Unique Machine ID |
| **description** | `string` | optional | |
| **contextSchema** | `Record<string, any>` | optional | Zod Schema for the machine context/memory |
| **initial** | `string` | ✅ | Initial State ID |
| **states** | `Record<string, { type: Enum<'atomic' \| 'compound' \| 'parallel' \| 'final' \| 'history'>; entry?: (string \| object)[]; exit?: (string \| object)[]; on?: Record<string, string \| object \| object[]>; … }>` | ✅ | State Nodes |
| **on** | `Record<string, string \| { target?: string; cond?: string \| object; actions?: (string \| object)[]; description?: string } \| { target?: string; cond?: string \| object; actions?: (string \| object)[]; description?: string }[]>` | optional | |

### Nested Shape: `Agent.planning`

| Property | Type | Required | Description |
Expand Down
3 changes: 1 addition & 2 deletions content/docs/references/automation/index.mdx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: Automation Protocol — schema reference
navTitle: Automation Protocol
description: "The ObjectStack Automation Protocol in 14 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
description: "The ObjectStack Automation Protocol in 13 reference pages: every schema in @objectstack/spec with its properties, types, defaults and a TypeScript example."
---

{/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */}
Expand All @@ -20,7 +20,6 @@ This section contains all protocol schemas for the automation layer of ObjectSta
<Card href="/docs/references/automation/node-executor" title="Node Executor" description="Source: packages/spec/src/automation/node-executor.zod.ts" />
<Card href="/docs/references/automation/schedule-organization" title="Schedule Organization" description="Source: packages/spec/src/automation/schedule-organization.zod.ts" />
<Card href="/docs/references/automation/schemaless-node-config" title="Schemaless Node Config" description="Source: packages/spec/src/automation/schemaless-node-config.zod.ts" />
<Card href="/docs/references/automation/state-machine" title="State Machine" description="Source: packages/spec/src/automation/state-machine.zod.ts" />
<Card href="/docs/references/automation/time-relative-trigger" title="Time Relative Trigger" description="Source: packages/spec/src/automation/time-relative-trigger.zod.ts" />
<Card href="/docs/references/automation/webhook" title="Webhook" description="Source: packages/spec/src/automation/webhook.zod.ts" />
</Cards>
1 change: 0 additions & 1 deletion content/docs/references/automation/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
"execution",
"flow",
"node-executor",
"state-machine",
"time-relative-trigger",
"---Integration & Data---",
"bpmn-interop",
Expand Down
Loading
Loading