Skip to main content

architecture

Reuse org catalog resources when deriving or enriching a component's design — deciding the component decomposition, filling a scaffolded design.json (language, dependencies, description, pinned skills), or resolving/reconsidering any dependency.

Datos de origen

Repositorio
wso2/labs-agentic-engineer
Última actividad en el origen
30 de septiembre de 2026 a las 16:37
Idioma detectado de SKILL.md
inglés
Estrellas
25
Forks
24

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
architecture
description
Reuse org catalog resources when deriving or enriching a component's design — deciding the component decomposition, filling a scaffolded design.json (language, dependencies, description, pinned skills), or resolving/reconsidering any dependency.
metadata
{"aep":{"kind":"platform","audience":["design"]}}
# Architecture Component decomposition and per-component facts. The cell (`cell-design` skill) declares WHAT exists; this skill owns deciding the decomposition and ENRICHING each component's `design.json`. ## Scaffold first, enrich second When the cell is saved, the platform scaffolds `specs/design/components/<id>/design.json` for every deployable component — the mechanical fields (name, type, version, buildpack, appPath, entrypoint, a default exposure) plus a `"language": "TBD"` sentinel. Your job is the JUDGMENT fields, and the build gate refuses a deployable component left unenriched: - **language** — **Language is decided in this order**: the organization skill's Tech stack default first, then a language the requirements name, then the platform default — **Ballerina** for backend services, TypeScript for web apps. Replace the scaffold's `"language": "TBD"` sentinel with the decided value; the build gate refuses a component whose language is still TBD. That choice is a fact you record, not a preference you re-derive per component: write it as the component's `language` and pin the matching stack skill in `skillsPinned`. - **stories** — the IDs of the stories this component serves, as written in the feature files (e.g. `"stories": ["F1.1", "F1.2", "F2.1"]`). Claim every story the component actually serves: the build gate refuses the tag while any story is claimed by no component, or an ID the requirements do not define is cited. - **dependencies** — the playbook below. - **description** — one paragraph: single responsibility, port/entrypoint expectations, and what it explicitly does NOT do. - **skillsPinned** — After emitting or changing a component's design, record the skills that component's build actually needs as a `skillsPinned` array **inside that component's `specs/design/components/<name>/design.json`** — use the exact catalog names, e.g. a Ballerina API service → `["openapi-conventions", "ballerina"]` (a Go one → `["openapi-conventions", "go"]`); a web-application → `["wireframes", "react-webapp", <design system>]`, where `<design system>` is the skill name in the **UI design system** section of the Organization defaults block in your instructions. A web app's UI toolkit is a settled organization decision, so pin it on **every** web-application rather than leaving it to a description to trigger — and read the name from that section every time instead of remembering one, because it differs per organization. If that section is absent or empty, pin only the two stack skills. Never substitute a design system the organization defaults do not name. **Pin the design system; do not consult it.** A design system is built against, not designed with — it is the coding run's to load, and its theming is a settled organization decision that no design-time question reopens. Never ask the user about colors, themes, or look and feel. An ai-agent → `["agent-building"]`. **Pin the auth skills the same way, never leave them to a description:** `"thunder-authentication"` on **both** sides of sign-in — every component that declares the `thunder-app` dependency, the SPA *and* each protected backend it calls — and `"api-management"` on every service that sits behind the gateway. The first owns the SPA's sign-in, its screen gates and the backend's verification of the gateway's assertion; the second owns the gateway's contract. It is a JSON key on the component's design object, so include it when you write that `design.json` (addFile/editFile) — `design.json` is its only home. Each component carries only the skills its own build needs. Writing the whole enriched file yourself (removeFile + addFile with every field) is equally valid — the scaffold is a safety net, not a required intermediate. ## Deriving components — deployment units the requirements justify A component is one independently deployable unit, NOT a domain concept. The right number comes from the requirements: for every component you must be able to say "this deploys and evolves independently because <something the requirements state>". Write that justification into the component's `description`. A requirement justifies a SEPARATE component when it shows: - a distinct user-facing surface — e.g. an internal admin portal AND a customer-facing app with different users and lifecycles → two web-applications; - a genuinely different runtime or scaling profile — e.g. an async worker/batch processor beside an interactive API, or a long-running AI/inference service; - a technology the rest of the system doesn't share — e.g. a Python ML service beside a Ballerina API; - an explicitly separate lifecycle or ownership stated in the requirements. Do NOT split by: - entity or domain concept — claims-service, users-service, receipts-service... is a domain model dressed as a topology; those are modules of ONE service; - layer — auth, notifications, file storage as own services when they are modules of the API; a single frontend split into `ui-shell` + `ui-pages` when it is one bundle; - infrastructure — api-gateway, database, queue, auth server are NEVER components; the platform provides them; - periodic work — a nightly digest or a cleanup sweep is a background task the owning service starts, not a component of its own. Only a genuinely different runtime or scaling profile (above) justifies splitting one off. **Never invent a user-facing surface the requirements did not ask for.** A system whose requirements describe a browser app lands at one service + one web-application; a system whose only surface is an agent lands at one service + one ai-agent and NO web-application. An agent is a complete surface on its own — the platform's Try it app talks to a deployed agent directly — so "there must be something for the user to open" is not a reason to add a SPA. Adding an unrequested web-application is the same failure as splitting by domain concept: a shape imposed on the requirements instead of read from them. Name components in kebab-case after their responsibility (`expense-api`, `expense-webapp`, `report-worker`, `packing-agent`). **An AI agent is `"ai-agent"`, and every model call is one.** It is the only component type with model access. Reach for it when the requirements call for a conversational or autonomous surface — a user talking to the system in their own words rather than filling in a form — and for every agent the requirements' decisions name. Its behaviour is authored as `agent.afm.md` (the `agent-building` skill), it is implemented in TypeScript, and it pins `["agent-building"]`. It is a normal deployable that calls other components over HTTP: give it a `component` dependency for every API it uses. **An agent a signed-in user reaches is a protected backend** — whether the caller is a sibling web-application or the platform's Try it app, set `"exposure": "internet"` (a browser cannot reach an intranet address, so an intranet agent has nothing that can talk to it) AND give it the project's shared `thunder-app` dependency, under the same dependency NAME the SPA and the sibling APIs use, exactly as you would for a service. It is the same sign-in and the same OAuth app, not a second one. Do this even when the agent stores nothing and every API behind it already authorises: a service you can call without a token costs CPU, an agent you can call without a token costs money on the organisation's model key, so an unauthenticated agent endpoint is a billing hole rather than a tolerable one. **Declare no dependency for model access** — every `ai-agent` gets it from its component type, on the organisation's own key, so there is nothing to choose. **An agent whose work needs files declares them.** Give it `x-aep.attachments` with the types and counts the feature needs (`agent-building` has the ceilings and the contract). One whose work runs with nobody signed in — a ticket from an arriving email, a nightly job — does not fit yet: ask the user at design altitude whether a person does that step for now or the feature waits. **An `ai-agent` with server memory needs a Postgres for its conversation store.** Give it a `platform-resource` dependency with `resourceType: "postgres-cnpg"` — the PVC-backed type, so a conversation survives a pod restart; the older ephemeral `postgres` type is not installed and referencing it fails provisioning. Dedicated is the default — `{ "kind": "platform-resource", "name": "memory-db", "resourceType": "postgres-cnpg" }`, injected as `MEMORY_DB_HOST` / `MEMORY_DB_PORT` / `MEMORY_DB_DBNAME` / `MEMORY_DB_USER` / `MEMORY_DB_PASSWORD` (`postgres-cnpg` has no `url` output; the generic platform-resource wiring injects each of its five outputs as `<DEP_NAME>_<OUTPUT>`, uppercased). When the project already carries a Postgres and the user prefers one instance, declare the SAME dependency name the sibling service uses — same-name resolution to one shared instance is the `thunder-app` sharing rule, and it applies here too. Either way the agent owns its `conversations` table exclusively: no other component touches it, and the agent touches nothing else in a shared instance. Business data is still not the agent's to hold — anything beyond its own conversation belongs behind a service that owns it. **Component `type` is a fixed vocabulary — use the EXACT string.** A backend is `"service"`; a browser app is `"web-application"` (OpenChoreo's own term). Write `"web-application"` verbatim — NOT `"webapp"`, `"web-app"`, or `"webApplication"` (those are rejected, and a wrong value silently breaks the app's deployment and runtime config). The `-webapp` in a component NAME is fine; the `type` is still `"web-application"`. Other kinds the requirements imply (`"scheduled-task"`, `"worker"`, …) are captured verbatim, but the platform installs component types for `service` and `web-application` ONLY — anything else records intent and does not deploy, so reach for one when the requirements truly force it, never as the default home for periodic work. ## Per-component design.json Each component's structured facts live in ONE JSON document (no markdown, no frontmatter). The platform validates each write against this schema and rejects violations: ```json { "name": "expense-api", // MUST equal the directory name "type": "service", // EXACT kind: "service" or "web-application" (NEVER "webapp"/"web-app"), or another the requirements imply ("scheduled-task", "worker", ...) "version": "0.1.0", // semantic version; 0.1.0 for a new component "language": "Ballerina", // implementation language — "Ballerina" for a service unless the requirements say otherwise; "TypeScript" for a web-application "buildpack": "docker", // always "docker" "appPath": "expense-api", // repo-relative source dir — the component name "entrypoint": "deployment/service", // deploy entry — PAIRS with `type`: "deployment/service" for a service, "deployment/web-application" for a web-application, "deployment/ai-agent" for an ai-agent "exposure": "internet", // "internet" (public) | "intranet" (internal only) "dependencies": [ /* see below — every dependency edge touching this component appears here */ ], "description": "One paragraph: single responsibility, port/entrypoint expectations, and what it explicitly does NOT do.", "stories": ["F1.1", "F1.2", "F2.1"], // story IDs THIS component serves — the build gate refuses the tag while any story is claimed by nobody "skillsPinned": ["openapi-conventions", "ballerina"], // the skills this component's build needs — see the field above "endpoint": { "name": "http" } // optional; see below } ``` `name`, `type`, `version`, `language`, `buildpack`, `appPath`, `entrypoint`, `exposure`, `description`, `dependencies`, `stories` and `skillsPinned` are required — `stories` and `skillsPinned` are as required as the rest, and a component missing either fails the build gate, so emit them in the SAME write rather than as a follow-up edit. To CHANGE a design.json, re-emit the whole corrected file (removeFile + addFile) — never patch JSON with anchored edits. On INVALID_JSON or SCHEMA_VIOLATION, fix what the message lists and re-emit. `endpoint` is optional: omit it and a service's endpoint takes the default name `"http"`. Declare `{ "name": "<endpoint-name>" }` only when the endpoint must be named otherwise — `name` is the single source of truth the coding agent copies into `workload.yaml` and the managed-API gateway binds to. The port lives in `workload.yaml`, not here. **Platform-owned fields you never author**, in two kinds: - **Preserved verbatim** where the platform has already written them: `exposesAPI`, `componentAgentInstructions`, and any dependency `status`/`reason`. - **Recomputed and overwritten** on every save: a dependency's `wiring` object. The platform derives its `ref` and its env-var names from the dependency's name and its resource type's declared outputs, so anything you write there is discarded. `stories` is NOT in this class — it is yours to author, per the **stories** field above. ### dependencies — one entry per design.cell edge `dependencies` mirrors the DEPENDENCY edges of design.cell: every arrow between this component and another node appears here and vice versa — a mismatch is a defect. The `north ->` / `west ->` gateway arrows are **exposure**, not dependencies: they live in this component's `exposure` field and never become entries here. The platform enforces the membership half as you write: a dependency whose `name` is not a node the cell declares is refused (`UNKNOWN_DEPENDENCY`) with the cell statement to add. A resource you discover here — a database, a cache — is a cell node too: `editFile` `specs/design/design.cell` to add `component <name> as "…" database` inside the cell before the design.json that depends on it, or the diagram will not draw what the build provisions. Each entry is a `kind` plus a `name`, and you pick the kind by WHAT the target is. The kind-only fields below are exhaustive: one of them on another kind is a schema violation that both the zod write-gate and the Go fold gate reject. ### The four kinds | `kind` | The target is | `name` comes from | Kind-only fields | Discover with | |---|---|---|---|---| | `component` | a SIBLING in this design that this component CALLS | the sibling's own name | — | this design | | `org-service` | a service ANOTHER project publishes for cross-project use | the provider's exact name, **copied verbatim** | — | `list_org_endpoints`, then `list_org_component_endpoints` | | `platform-resource` | a backing resource the platform provisions (database, cache, IDP) | **your choice** — it becomes the env-var prefix | `resourceType` (a registered type), `parameters` | `list_platform_resource_types` | | `external` | a system OUTSIDE the platform (a SaaS API, a legacy service) | a **Registered External resource**'s exact name, else a **new** name for a **Project External resource** | none on the component — the definition is the dependency's own file (below) | `list_external_resources` + `get_external_resource_schema`, else `web_search` | **Reuse.** Call that last column's tool before authoring the entry, and take the name and schema from the matching row rather than from the requirement's wording — a **Registered External resource** described as "transactional email delivery" is the right reuse for an "email" need even when its name (`sendgrid`) doesn't echo the requirement. When several rows could fill the role, the org-level one wins (a Registered External over a new Project External name; an org-service over a sibling you would otherwise add; a listed cluster resource type for `resourceType`). This step is done when every `external`, `org-service`, and `platform-resource` emitted this turn is taken from this turn's matching `list_*` result (exact `name` or `resourceType`), unless this turn is a user-asked reconsider. When nothing the catalog returns fills the role, omit the entry and list the gap under **Needs your input** rather than coining a name: a `resourceType` that was not in this turn's `list_platform_resource_types` result fails Build. ```json "dependencies": [ { "kind": "component", "name": "expense-api" }, { "kind": "platform-resource", "name": "orders-db", "resourceType": "postgres-cnpg" }, { "kind": "external", "name": "github", "description": "Call GitHub issues + PRs for the sync story." } ] ``` The `github` entry is a REFERENCE: its definition — provider, style, contract, config keys — is `specs/design/dependencies/github/dependency.json`, written once and shared by every component that uses it. A component's `description` on the edge says why THIS component uses it; the dependency's own description says what the system is. #### Reading a provider's real contract Once you have an `org-service` provider's name, call `list_org_component_endpoints` and base that dependency's `description` on the operations its contract actually exposes: | `spec.availability` | Where the contract is | |---|---| | `inline` | `spec.inlineContent` IS the OpenAPI document — read it directly | | `repo` | the row's `owner`/`repo`/`subdir`/`branch` locate the source: `search_remote_git_code` under that `subdir`, then `get_remote_git_file_contents` | | `none` | nothing resolvable — say so plainly in the `description`, never invent a shape | ### Traps - **Never the reverse edge.** Declare a dependency ONLY on the caller, naming the callee: a web-app depends on the API it calls; the API does not depend on the web-app. A component this one doesn't call is not a dependency of it — don't list one "for reference". - **A role is not a name.** The requirement says "the organization's directory service"; the provider is usually called something else (`employee-service`). The same for a `platform-resource`: the PRD names a capability, `resourceType` is the catalog row's `name` from this turn's `list_platform_resource_types`. Copy that `name`. Look it up — a name coined from the role or capability words matches no provider or type and hard-fails the build. - **A `platform-resource`'s `name` becomes the env-var prefix** for every one of its outputs (`orders-db` → `ORDERS_DB_HOST`, and for a SPA `window._env_.ORDERS_DB_*`), so pick a clear one: renaming it later renames the component's whole config surface. - **Secret-bearing dependencies belong on a `service`, not a `web-application`.** A web-application ships to the browser, so anything it holds is visible in dev tools — and *every* output of a `platform-resource` it declares is emitted into `window._env_`, so a database dependency on a SPA publishes its password. The secure default: attach the dependency to a backend `service` and give the web-app a `component` edge to it, so the service proxies and the SPA never sees the credential. A web-application may declare an `external` dependency directly only when NONE of its `config` keys need `secret: true` — a genuinely public API, or one the END USER authenticates with their own in-browser OAuth. The one exception is `thunder-app` (next bullet): its outputs are public OIDC client config by design. The schema does not reject a secret on a web-application, so apply this as the architect's judgment call, not a rule to route around. - **`thunder-app` is how sign-in happens, and nothing else provisions it.** When the spec implies users sign in, declare it on BOTH the SPA and each protected service under the SAME dependency `name` — that shared name is what ties sign-in to token-carrying API calls. With no such dependency the SPA deploys unable to sign in. `thunder-app` takes no `parameters` and **nothing about the sign-in client is authored by hand**: its display name, its redirect URIs and its scope list are all derived by the platform (the scopes from the permission
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub