# Manifest schema

Every Powerloom resource is declared as YAML and applied through the same `weave plan` / `weave apply` path. This page lists every supported `Kind`, the fields each one takes, and where defaults come from.

The canonical schema source lives in the `loomcli` repository — `pip install loomcli` ships JSON Schema for every Kind. `weave validate -f <file>` checks any manifest against the bundled schema before you apply.

---

## Manifest envelope

Every manifest looks the same on the outside.

```yaml
apiVersion: powerloom/v1
kind: <Kind>
metadata:
  ...kind-specific fields...
```

`apiVersion` is `powerloom/v1` for every resource currently shipping. The schema versioning lives at the resource level — older manifests stay applicable across CLI bumps until a `powerloom/v2` cutover lands (none planned today).

`kind` is one of the eleven listed below.

`metadata` carries the resource definition. Powerloom doesn't separate spec from metadata the way Kubernetes does; everything you declare lives at the top level under `metadata` to keep the file flat and the diff small.

Multi-document files (`---` separator between manifests) work — `weave apply -f bundle.yaml` applies them in order.

---

## Kind: OU

Organizational unit. Container for principals, agents, MCP deployments, and credentials.

```yaml
apiVersion: powerloom/v1
kind: OU
metadata:
  name: engineering              # required, DNS-label-shaped
  parent_ou_path: /acme          # required for non-root; root is implicit
  display_name: Engineering      # optional, free-form
  description: Engineering teams # optional
```

`parent_ou_path` resolves the parent at apply time. The first OU you create in a workspace lives at root; everything else nests below something.

---

## Kind: Group

Security group. Holds principals (users, other groups, OUs).

```yaml
apiVersion: powerloom/v1
kind: Group
metadata:
  name: eng-leads
  scope_ou_path: /acme/engineering
  display_name: Engineering leads
  description: Folks who can approve agent deployments to engineering
```

Membership is managed via `GroupMembership` (next).

---

## Kind: GroupMembership

A user (or another group) added to a group.

```yaml
apiVersion: powerloom/v1
kind: GroupMembership
metadata:
  group_ref: group:/acme/engineering/eng-leads
  member_ref: user:alice@acme.com
```

`member_ref` accepts `user:`, `group:`, or `ou:` prefixes. Nested groups resolve transitively through a closure table; permission checks stay constant-time regardless of nesting depth.

---

## Kind: RoleBinding

Grants or denies a role at a scope. The core primitive of RBAC.

```yaml
apiVersion: powerloom/v1
kind: RoleBinding
metadata:
  principal_ref: group:/acme/engineering/eng-leads
  role: OUAdmin
  scope_ou_path: /acme/engineering
  decision_type: allow            # 'allow' | 'deny' — deny wins
  conditions:                     # optional, role-specific
    tool_names: [query, describe_schema]
```

Built-in roles: `OrgAdmin`, `OUAdmin`, `AgentAuthor`, `DeploymentOperator`, `Viewer`, `ToolUser`. Custom roles via `Role` manifests (rare; the built-ins cover most needs).

`scope_ou_path` is the inheritance anchor — a binding at `/acme/engineering` applies to that OU and every descendant. For binding to a specific resource (e.g., one MCP deployment), use `scope_resource:` instead.

Deny always wins at the same scope. A `deny` binding at `/acme/engineering` overrides an `allow` at the same scope; an `allow` at `/acme/engineering/platform` doesn't override the parent's deny.

---

## Kind: Agent

A Claude (or other-runtime) agent registered under an OU.

```yaml
apiVersion: powerloom/v1
kind: Agent
metadata:
  name: code-reviewer
  scope_ou_path: /acme/engineering
  display_name: Code reviewer
  description: Reviews PRs for the platform team
  model: claude-sonnet-4-6        # any supported runtime model id
  agent_kind: cma                 # 'cma' | 'cc' | 'direct' (most users: cma)
  system_prompt: |
    Multi-line system prompt.
    Indented under |.
  attached_skill_refs:            # optional list of Skill resources
    - skill:/acme/engineering/python-lint
  attached_mcp_refs:              # optional list of MCP deployments
    - mcp-deployment:/acme/engineering/dev-postgres
  capability_tags:                # optional, used by workflow agent nodes
    - pr_review
    - code_summary
```

The reconciler picks new agents up within seconds and registers them with the upstream runtime (Anthropic CMA by default). Status flips from `pending` to `synced` once the round-trip succeeds.

---

## Kind: Skill

A versioned bundle of code, docs, or prompts that an agent can attach. Skills land in S3 as archives; agents reference them by name.

```yaml
apiVersion: powerloom/v1
kind: Skill
metadata:
  name: python-lint
  scope_ou_path: /acme/engineering
  display_name: Python lint runner
  description: Wraps ruff + pyright into a single tool an agent can call
  archive_ref: skill-archive:/acme/engineering/python-lint/v3
```

The archive is uploaded separately via `weave skill upload` (versioned each time). The `Skill` manifest just points at the latest version.

---

## Kind: MCPDeployment

A running MCP server scoped to an OU. See [/docs/mcp-deployment](/docs/mcp-deployment) for the deployment model.

```yaml
apiVersion: powerloom/v1
kind: MCPDeployment
metadata:
  name: support-postgres
  scope_ou_path: /acme/support
  template: postgres
  parameters:
    connection_string_credential_ref: cred:/acme/support/support-readonly-db
    schema: public
  description: Read-only access to the support database
```

`template` selects from the catalog (15 today). `parameters` shape is template-specific — `weave describe template/<name>` shows the schema.

---

## Kind: MCPServerRegistration

Registers a pre-existing MCP server (one you run yourself, not via a Powerloom-managed deployment) so it shows up in agent attachment lists.

```yaml
apiVersion: powerloom/v1
kind: MCPServerRegistration
metadata:
  name: internal-orders
  scope_ou_path: /acme/support
  endpoint_url: https://orders-mcp.internal.acme.com/mcp
  auth_credential_ref: cred:/acme/support/orders-bearer
  tool_schema:
    - name: lookup_order
      input_schema: { ... }
```

Use this when you have an MCP server already running outside Powerloom and just want to govern *who can call its tools* without taking over the runtime.

---

## Kind: Credential

A secret (API key, bearer token, connection string) referenced by other resources. Powerloom never returns the raw value over the API after creation; only wrapped ciphertext.

```yaml
apiVersion: powerloom/v1
kind: Credential
metadata:
  name: support-readonly-db
  scope_ou_path: /acme/support
  description: Read-only Postgres for support agents
  value_envelope_ref: secrets-manager:arn:aws:secretsmanager:us-east-2:1234:secret:abc/support-db
```

Credentials carry only a *reference* to where the secret lives. Powerloom resolves the reference at use-time, decrypts under the per-tenant KMS CMK, hands the plaintext to whatever consumer needs it, and never logs the cleartext.

For a key you want to paste in directly (vs reference an existing AWS Secret), use the console at `/settings/credentials` — the value is encrypted in-flight and never round-trips through your manifest tree.

---

## Kind: ApprovalPolicy

Declares which mutations require an approver. Used by the Phase 12 approval gate.

```yaml
apiVersion: powerloom/v1
kind: ApprovalPolicy
metadata:
  name: production-agent-creation-needs-approval
  scope_ou_path: /acme/production
  resource_kind: agent
  action: create
  approver_role: OUAdmin
  ttl_seconds: 86400
  justification_required: true
```

Any matching mutation (here: creating an `Agent` anywhere in `/acme/production`) lands in `pending` until an approver in `OUAdmin` scope acts. Auto-deny fires after `ttl_seconds`.

---

## Kind: WorkflowDefinition

A declarative DAG. See [/docs/workflows](/docs/workflows) for the node kinds.

```yaml
apiVersion: powerloom/v1
kind: WorkflowDefinition
metadata:
  name: pr-triage
  scope_ou_path: /acme/engineering
  description: Review + approve + merge incoming PRs
spec:
  nodes:
    - node_id: start
      kind: trigger
      next: review
    # ... rest of the DAG ...
```

Workflows are the only Kind that uses `spec:` instead of putting everything under `metadata` — the `nodes` array is large enough that nesting it makes the diff easier to read.

---

## Defaults and immutability

A few rules that apply across every Kind:

- **Names are DNS-label-shaped** — lowercase alphanumeric and hyphens, 1-63 chars, no leading/trailing hyphen.
- **`name + scope_ou_path` is the unique identity** — re-applying a manifest with the same name + scope is an update; with a different scope is an error.
- **Some fields are immutable after create** — `name`, `parent_ou_path`, `agent_kind`, `template`. Changing them requires destroy + recreate.
- **Defaults apply at apply-time, not load-time** — a manifest with no `description` lands with `description: null`, not the empty string.

`weave plan` shows the full diff (including defaults that would be filled in) before any apply. Always safe to run.
