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
66 changes: 66 additions & 0 deletions .changeset/retire-agent-generator.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
---
"@objectstack/cli": minor
---

fix(cli): **BREAKING** — the `agent` generator is retired, and `os g agent` now says why and points at skills (ADR-0063 §2, #10359)

**⛔ If a script, a Makefile or a CI step in your project runs `os g agent`, it
will now exit 1.** That is the intended outcome and the one way this change can
interrupt you: the command is gone, deliberately, and the failure is how you
find out. Everything it used to produce was already being discarded — read on.

`minor`, not `major`: during the launch window this stack ships breaking changes
as `minor` (pre-1.0 semantics under lockstep versioning — see
`scripts/check-changeset-no-major.mjs`).

**What the command actually did.** `os g agent <name>` scaffolded a typed
`AI.Agent` into `src/agents/`. Per ADR-0063 §2 (which reversed ADR-0040 §3) the
kernel ships exactly **two** agents — `ask` and `build` — bound by surface and
never picked from a roster, and the runtime catalog **filters out every
non-platform agent record**. So the scaffolded file parsed, passed
`os validate`, published without complaint, and then never appeared anywhere.
No error at any step. An author who followed the documented example got a file,
a green validate, a successful publish, and nothing to show for it.

**Why the roster entry was not simply deleted.** A deleted type falls through to
`Unknown type: agent` plus a list of what is left, which tells the author their
spelling is not on the list and invites them to hunt for the right spelling of
something that no longer exists — the same silence, one step earlier. `agent` is
now a **retirement ledger entry** instead, and the refusal carries both halves:
the decision that withdrew the surface, and the surface to author in its place.
What you see:

```
✗ `os g agent` was retired — agents are platform-internal (ADR-0063 §2).

The kernel ships exactly two agents, `ask` and `build`, bound by surface.
An agent you author still parses and still publishes — and the runtime
catalog then filters it out, so it never appears and nothing tells you.
This command scaffolded exactly that file, so it is retired, not repaired.

Author a SKILL instead. Skills (plus tools / MCP) are the third-party
extension primitive ADR-0063 names — the live surface this one was not.

There is no `os g skill` scaffolder yet. Write the file by hand:

src/skills/<name>.skill.ts
import { defineSkill } from '@objectstack/spec/ai';

Docs: https://objectstack.ai/docs/ai/agents
```

**There is no command to rewrite the call into.** A skill is a different
artifact with a different schema, not a renamed agent, and `os g skill` does not
exist yet — the skill scaffolder is tracked separately, and this message says so
rather than promising it. Delete the call; author the skill file by hand.

The generator roster is now `object`, `view`, `action`, `flow`, `dashboard`,
`app`. The docs that advertised the retired one — the `os g agent support`
example, the `agent` / `src/agents/` row of the Available types table, and
`os g agent sales-assistant` in the Typical Workflow block — are gone from
`content/docs/deployment/cli.mdx`, which carries the retirement note instead;
`packages/cli/README.md`'s type roster follows. The quick-start project-layout
map, which listed `src/agents` as the directory an app author writes AI metadata
into, now names `src/skills`.

<!-- adr-0087: not-required (no-migration-prescription) A CLI COMMAND NAME is an invocation surface, not authorable metadata. There is no authorable key, no `sys_metadata` row and no schema to tombstone here, so there is nothing for `objectstack migrate meta` to rewrite, nothing for `spec-changes.json` to project and no FROM -> TO spelling for the upgrade guide to carry: a skill is a different artifact rather than a renamed agent, and no replacement command exists to rewrite the call into. Nor is the ledger the only notification channel this time, which is the difference from `http-request-errors-total-retired` (where an operator's Grafana panel silently drew a flat zero and the entry was the sole way to say so): the command itself now refuses, exits 1, names ADR-0063 and points at skills at the exact moment and place of use. Same reasoning shape as ADR-0087's D7 addendum, one surface over — there the compiler carries the notice, here the CLI does. -->
17 changes: 14 additions & 3 deletions content/docs/deployment/cli.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -1027,7 +1027,6 @@ os g object customer # Generate a Customer object
os g view customer # Generate a Customer list view
os g action approve # Generate an action
os g flow customer # Generate an automation flow
os g agent support # Generate an AI agent
os g dashboard sales # Generate a dashboard
os g app crm # Generate an app definition

Expand All@@ -1043,10 +1042,23 @@ os g object task --dry-run # Preview without writing
| `view` | `src/views/` | List or form view definition |
| `action` | `src/actions/` | Button or batch action |
| `flow` | `src/flows/` | Automation flow |
| `agent` | `src/agents/` | AI agent |
| `dashboard` | `src/dashboards/` | Analytics dashboard |
| `app` | `src/apps/` | Application navigation |

<Callout type="warn" title="`os g agent` is retired">
There is no `agent` type. Running `os g agent <name>` fails with a message
naming [ADR-0063](https://github.com/objectstack-ai/objectstack/blob/main/docs/adr/0063-two-kernel-agents-skills-are-the-extension-primitive.md)
and pointing at skills, rather than the generic "unknown type" listing.

Agents are **platform-internal**: the kernel ships exactly two (`ask` and
`build`), and the runtime catalog filters out every other agent record. A
scaffolded `src/agents/*.ts` therefore passed `os validate`, published without
complaint, and never appeared — silently. Skills (plus tools / MCP) are the
third-party extension primitive, authored as `src/skills/<name>.skill.ts` with
`defineSkill`; see [AI Agents](/docs/ai/agents). There is no `os g skill`
scaffolder yet, so write that file by hand for now.
</Callout>

**Options:**
- `-d, --dir <directory>` — Override target directory
- `--dry-run` — Preview without writing files
Expand DownExpand Up@@ -1583,7 +1595,6 @@ os g object opportunity

# 3. Add business logic
os g flow lead-qualification
os g agent sales-assistant

# 4. Validate everything
os validate
Expand Down
2 changes: 1 addition & 1 deletion content/docs/getting-started/quick-start.mdx
Original file line numberDiff line numberDiff line change
Expand Up@@ -37,7 +37,7 @@ a project:
| **Automation** | Flows, workflows, triggers, approvals (`src/flows`) | Event handlers, approval chains, scheduled jobs |
| **Interface** | Views, apps, dashboards, actions (`src/views`, `src/apps`) | The Console UI, navigation, responsive layout |
| **Access** | Roles, permissions, sharing, row-level security | Middleware, RLS policies, field masking |
| **AI** | Agents, tools, RAG, MCP exposure (`src/agents`) | Chat, search indexes, an MCP server |
| **AI** | Skills, tools, RAG, MCP exposure (`src/skills`) | Chat, search indexes, an MCP server |

The through-line: **you author intent once as metadata, and the runtime derives the
database, the API, the UI, and the AI tool surface from it.** That's why an agent
Expand Down
8 changes: 7 additions & 1 deletion packages/cli/README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,7 +59,13 @@ os compile
| `os generate <type> <name>` | Generate metadata files (alias: `os g`) |
| `os create <type> [name]` | Create a new package/plugin/example from template |

Available generate types: `object`, `view`, `action`, `flow`, `agent`, `dashboard`, `app`
Available generate types: `object`, `view`, `action`, `flow`, `dashboard`, `app`

`agent` is **retired** (ADR-0063 §2): agents are platform-internal, so a scaffolded
`src/agents/*.ts` validated, published and was then filtered out of the runtime
catalog without a word. `os g agent` now says so and points at skills — the
third-party extension primitive — which are authored as `src/skills/<name>.skill.ts`
with `defineSkill`. There is no `os g skill` scaffolder yet.

### Cloud — publish & install

Expand Down
93 changes: 67 additions & 26 deletions packages/cli/src/commands/generate.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,7 +4,7 @@ import { Args, Command, Flags } from '@oclif/core';
import chalk from 'chalk';
import fs from 'fs';
import path from 'path';
import { printHeader, printSuccess, printError, printInfo, printStep, createTimer } from '../utils/format.js';
import { printHeader, printSuccess, printError, printInfo, printStep, createTimer, CLI_ALIAS } from '../utils/format.js';

// ─── Metadata Type Templates ────────────────────────────────────────

Expand DownExpand Up@@ -125,30 +125,6 @@ export default ${toCamelCase(name)}Flow;
`,
},

agent: {
description: 'AI agent',
defaultDir: 'src/agents',
generate: (name: string) => `import * as AI from '@objectstack/spec/ai';

/**
* ${toTitleCase(name)} Agent
*/
const ${toCamelCase(name)}Agent: AI.Agent = {
name: '${toSnakeCase(name)}_agent',
label: '${toTitleCase(name)} Agent',
role: '${toTitleCase(name)} assistant',
instructions: 'You are a helpful ${toTitleCase(name).toLowerCase()} assistant.',
model: {
provider: 'openai',
model: 'gpt-4o',
},
tools: [],
};

export default ${toCamelCase(name)}Agent;
`,
},

dashboard: {
description: 'Analytics dashboard',
defaultDir: 'src/dashboards',
Expand DownExpand Up@@ -189,6 +165,58 @@ export default ${toCamelCase(name)}App;
},
};

// ─── Retired Generators ─────────────────────────────────────────────

/**
* Scaffolder types that were withdrawn, and what this command says when one
* of them is run.
*
* A retired type is NOT an unknown type, and deliberately does not fall
* through to the `Unknown type:` branch in {@link runMetadataGeneration}.
* That branch prints the surviving roster and nothing else, so an author
* arriving from a doc page, a tutorial or a CI script that still names the
* retired type would learn only that their spelling is not on the list —
* and the natural next move is to hunt for the right spelling of something
* that no longer exists.
*
* `agent` (ADR-0063 §2, which reversed ADR-0040 §3): the kernel ships exactly
* two agents, `ask` and `build`, bound by surface and never picked from a
* roster. Tenant / app-package agents were withdrawn, and the runtime catalog
* filters out every non-platform agent record. The file this generator wrote
* into `src/agents/` therefore passed `os validate`, published without
* complaint, and was then dropped on the floor: no error at any step, the
* agent simply never appeared. Retiring the command silently would have moved
* that silence one step earlier instead of ending it, which is why each entry
* owes both halves — the decision that withdrew the surface, and the surface
* to author instead.
*/
const RETIRED_GENERATORS: Record<string, {
/** Reason clause completing "`os g <type>` was retired — …". */
reason: string;
/** Body lines, printed in order; an empty string prints a blank line. */
detail: string[];
}> = {
agent: {
reason: 'agents are platform-internal (ADR-0063 §2).',
detail: [
'The kernel ships exactly two agents, `ask` and `build`, bound by surface.',
'An agent you author still parses and still publishes — and the runtime',
'catalog then filters it out, so it never appears and nothing tells you.',
'This command scaffolded exactly that file, so it is retired, not repaired.',
'',
'Author a SKILL instead. Skills (plus tools / MCP) are the third-party',
'extension primitive ADR-0063 names — the live surface this one was not.',
'',
'There is no `os g skill` scaffolder yet. Write the file by hand:',
'',
' src/skills/<name>.skill.ts',
" import { defineSkill } from '@objectstack/spec/ai';",
'',
'Docs: https://objectstack.ai/docs/ai/agents',
],
},
};

// ─── Helpers ────────────────────────────────────────────────────────

function toCamelCase(str: string): string {
Expand DownExpand Up@@ -307,6 +335,19 @@ function generateTypesFromConfig(config: Record<string, unknown>): string {
async function runMetadataGeneration(type: string, name: string, flags: { dir?: string; dryRun?: boolean }): Promise<void> {
printHeader('Generate');

// A withdrawn type answers for itself, ahead of the roster lookup — see
// RETIRED_GENERATORS for why "unknown type" is the wrong answer here.
const retired = RETIRED_GENERATORS[type];
if (retired) {
printError(`\`${CLI_ALIAS} g ${type}\` was retired — ${retired.reason}`);
console.log('');
for (const line of retired.detail) {
console.log(line ? chalk.dim(` ${line}`) : '');
}
console.log('');
process.exit(1);
}

const generator = GENERATORS[type];
if (!generator) {
printError(`Unknown type: ${type}`);
Expand DownExpand Up@@ -874,7 +915,7 @@ export default class Generate extends Command {
static override aliases = ['g'];

static override args = {
type: Args.string({ description: 'Metadata type to generate (object, view, action, flow, agent, dashboard, app)', required: true }),
type: Args.string({ description: 'Metadata type to generate (object, view, action, flow, dashboard, app)', required: true }),
name: Args.string({ description: 'Name for the metadata (use kebab-case)', required: false }),
};

Expand Down
Loading
Loading