Skip to content

Catalog

The catalog is Factoryal's context lake: what exists, who owns it and what depends on what. Agents read it, workflows write to it, and every change can start a run. Its model follows Port.io's (blueprints, entities, relations, search), and the API shapes are Port-compatible.

erDiagram
    team ||--o{ service : owns
    service ||--o{ repo : "built from"
    service ||--o{ issue : "affected by"
    repo ||--o{ issue : "fixed in"

Blueprints

A blueprint is a type: a JSON schema for its properties, plus named relations to other blueprints.

kind: Blueprint
identifier: service
title: Service
schema:
  properties:
    tier: { type: number, description: "1 = business critical" }
    language: { type: string }
    lifecycle: { type: string, enum: [experimental, production, deprecated] }
  required: [lifecycle]
relations:
  owner: { target: team, required: true }      # many: true for a list

Property types are string, number, boolean, object and array. Other JSON-schema keywords (enum, format, minimum, items, ...) pass through and are enforced. The full sample catalog is examples/demo/catalog/catalog.yaml.

Action API
Create / replace / delete catalog/*.yaml in a factory repo (kind: Blueprint)
Read / list GET /v1/blueprints/<id>, GET /v1/blueprints

The catalog holds two kinds of thing, told apart by where they come from:

  • Factory config: agents, skills, rules, runners and webhook sources. They're defined in a factory repo and are not part of the catalog: they live in their own tables (the Postgres schema config) and have their own read endpoints (Agents). Before that they were entities of the blueprints _agent, _skill, _rule, _environment and _webhook, which no longer exist.
  • Data: everything else, written through the API, by workflows and by integrations. This includes agents' memory lessons (_memory).

If it's in your factory repo, it's config; everything else is data. Blueprints whose names start with _ are platform-managed: admin-only, and hidden from the Catalog page unless you ask for them. Integrations create the blueprints they need on first use (slack_message, linear_issue).

Entities

kind: Entity
blueprint: repo
identifier: billing-api
title: Billing API
team: payments                     # one or a list; drives ownership, budgets and $team rules
properties:
  url: https://github.com/acme/billing-api
  default_branch: main
  has_readme: true
relations: { service: billing }
Action API
Create POST /v1/blueprints/<bp>/entities
Replace / merge PUT / PATCH /v1/blueprints/<bp>/entities/<id>
Read / list / delete GET, DELETE /v1/blueprints/<bp>/entities/<id>, GET /v1/blueprints/<bp>/entities

Every write is validated against the blueprint. It is committed in the same transaction as an outbox event (entity.created, entity.updated, entity.deleted), so entity_event triggers see every change exactly once (Architecture).

POST /v1/entities/search (or /v1/blueprints/<bp>/entities/search) takes Port's query shape. Agents use the same language through their search_catalog tool.

{
  "combinator": "and",
  "rules": [
    { "property": "$blueprint", "operator": "=", "value": "service" },
    { "property": "tier", "operator": "<=", "value": 1 },
    { "combinator": "or", "rules": [
        { "property": "language", "operator": "=", "value": "python" },
        { "property": "$team", "operator": "containsAny", "value": ["payments"] }
    ]}
  ]
}
Rule Form
Property {property, operator, value}. property is a property name or $identifier, $title, $blueprint, $team, $createdAt, $updatedAt
Operators =, !=, >, >=, <, <=, in, notIn, contains, doesNotContains, containsAny, beginsWith, endsWith, isEmpty, isNotEmpty, between
Graph {operator: relatedTo, blueprint, value, direction}: entities connected to the given ones, transitively (up to 10 hops)
Groups {combinator: and \| or, rules: [...]}, nested freely

For relatedTo, direction: upstream follows relations outward (what the given entities depend on) and downstream follows them inward (what depends on them). Without a direction it goes both ways. For example, every issue and repo that hangs off the billing service:

{ "rules": [{ "operator": "relatedTo", "blueprint": "service", "value": "billing", "direction": "downstream" }] }

The factory scorecard

Every applied factory is graded on which Factoryal features it configures, so it's clear what it isn't using yet. About 45 checks in eight areas:

Area What it checks
Agents agents exist, pin a model, hold no unrestricted repo:write / catalog:write
Skills and rules agents load skills, no skill sits unused, skill variants are compared in a benchmark, an org rule exists, repo/team rules when code changes
Memory and learning lessons are recorded (by retros, or agents with memory:write on an agent step, which actually did in the last 30 days), steps read them back (memory: or a code step), the loop is closed, every fixed subject read is one an agent may write, agents reading webhook or entity payloads use memory_review: always, the review queue isn't stale; and the learning loop: runs that go wrong get a retro (from run_event), so do PRs reviewers turn down (outcome), an improver with proposals:write turns each new episode of a _cluster into a proposed fix, retro spend is capped, and retros were actually written for recent failures
Automation workflows start from events, every webhook source is used, something runs on a schedule, results are posted back (notify)
Guardrails factory and project budgets, a budget_event workflow, a person approving before code that opens PRs, max_cost_usd, timeout on llm, agent, code and http steps, concurrency on event-driven workflows
Coding code steps set a runner (environment); pull requests get an agent review
Quality loop benchmark suites with tasks for every coding agent, run on a schedule, with a benchmark_event alert
Operations (live only) from the last 30 days: work is running, runs succeed (from 5 finished runs), approvals are answered and mostly accepted (from 3 decided), spend is within budget, every suite ran recently without regressing (only runs launched while this factory owned the suite count; a run of a suite no factory owned, or one an earlier factory owned, isn't counted, so a suite this factory just took over shows as not run in the last 30 days until its next run)

A person approves before code is written when the workflow has an approval or wait step, or when every automatic trigger is a webhook or entity_event whose condition checks a status, label or approval a person sets (an issue moved to Ready for Dev).

Each check is pass, partial, missing or n/a (it doesn't apply, e.g. runner checks without code steps), weighs by impact (high 3, medium 2, low 1), and lists the resources it found. The score is the weighted share passed (partial counts half): Basic below 40, Bronze from 40, Silver from 65, Gold from 85. Two levels also have a gate:

  • Silver needs a closed learning loop: lessons recorded (by agents or retros) that steps read back. A factory that doesn't learn stays at Bronze.
  • Gold needs every high-impact check passing.

When a gate holds a factory back, capped says which level the score reached and what is missing. Failing checks come back as recommendations, highest impact first, each saying what to add to the factory repo. The Learning inbox ranks them with everything else waiting on a person.

Projects. Each project is graded on its own: what it owns plus the shared resources at the factory's root (agents, skills, rules, runners, webhooks, catalog, benchmarks), and the shared workflows its steps start. Its budget check looks at its own project: budget, and its live checks at its own runs.

Trend. Every apply saves the factory's score and level (from its configuration alone) with the version, so trend shows how each commit moved it, oldest first.

  • GET /v1/factories/<name>/scorecard (admins only: it shows agents' scopes, costs and failures) grades the live factory, with runtime signals such as lessons waiting for review, one grade per project under projects, and trend; GET /v1/factory-scorecards grades every factory (an empty list for anyone but admins). Each is cached for 30 seconds. The web UI shows them at the top of Scorecards, with a tab per project and the trend next to the score.
  • factoryal factory check <dir> prints the same scorecard for a folder, before you push it.

Where catalog data comes from

Source Writes
Factory repos blueprints and seed entities (catalog/)
The API entities of your own blueprints
Workflows upsert_entity steps, and agent steps with catalog:write[:<bp>]
GitHub App repo facts (has_readme, has_ci, main_ci_status, ...) on pushes to the default branch and on CI results (GitHub App)
Slack / Linear slack_message and linear_issue entities (Slack and Linear)
Webhook sources one entity per delivery, through entity: mapping (Factories)