Morphic Agent Framework

concept

The ideas somebody has to understand — starting with the program itself.

export concept Editor:
    description: How the editor fits together.

    overview:
        A document is a tree of layers rendered onto a single canvas. Tools
        mutate the document; the renderer never does. Every mutation goes
        through the command stack, which is what makes undo total rather than
        best-effort.

A concept is an idea somebody has to understand before they can work on the software.

Every project has at least one: the root concept, which answers "what is this program, and why is it shaped like that?" It is the first thing an agent reads and the last thing you should let go stale.

But it is not the only one. Anything bigger than a single feature and less specific than a domain is a concept too — the document model, coordinate spaces, how undo works, what "eventually consistent" means here. Those are ideas that cut across domains and cannot be understood by reading any one component. They are exactly the things you end up explaining on a whiteboard.

overviewstring, required

The shape of the program, in prose.

Not what it does for users (that is a feature) and not how the code is arranged (that is a domain). The model: what the central objects are, how they relate, and what the load-bearing rule is.

A good overview usually has three moves in it:

  1. The core objects and their relationship. "A document is a tree of layers rendered onto a single canvas."
  2. The central constraint. "Tools mutate the document; the renderer never does."
  3. Why. "…which is what makes undo total rather than best-effort."

That third one is the one people skip, and it is the one that stops an agent "simplifying" your architecture. A rule with a reason survives contact with someone trying to be helpful. A rule without one reads like an accident.

How many

One root concept, plus one for every idea that needs explaining.

The root concept describes the whole program. There is one of those, or two if the product genuinely has two halves a person would describe separately — a CLI and a daemon, an editor and a renderer that ships on its own.

Beyond that, write a concept whenever you hit an idea that is:

  • bigger than a feature — it is not one behaviour a user can point at, and
  • less specific than a domain — it is not an area you would assign work to, and it usually spans several of them, and
  • not a rule — it describes how something works, not how work should be done. Rules are references.

The test is the whiteboard. If onboarding somebody involves drawing it, it is a concept. If explaining it involves saying "so the way X works here is…", it is a concept.

There is no upper bound worth stating. A large system might have a dozen. What you should not have is a concept that is really an area of work — that is a domain wearing the wrong keyword.

Root concepts

The one that describes the whole program.

A desktop application

use "@psy/morphic"

export concept Editor:
    description: How the vector editor fits together.

    overview:
        A document is a tree of layers rendered onto a single canvas. Tools
        mutate the document; the renderer never does. Every mutation goes
        through the command stack, which is what makes undo total rather than
        best-effort.

        The document is the only source of truth. Anything on screen is derived
        from it and can be thrown away and recomputed.

    constraints:
        - The renderer is pure: document in, pixels out
        - No tool touches the canvas directly
        - Every user-visible action is undoable

    nonGoals:
        - Real-time collaboration. The document model is single-writer and changing that is a rewrite, not a feature.
        - Raster editing. Bitmaps are placed, never edited.

constraints and nonGoals are not properties Morphic knows about. They became sections anyway — anything you declare does.

nonGoals earns its place on a concept more than anywhere else. It is the fastest way to stop an agent building something you have already decided against, and the reason is right there in the line.

A backend service

export concept Platform:
    description: How services on our platform fit together.

    overview:
        Every service is a single deployable that owns its data outright. No
        service reads another service's tables. Services communicate by
        publishing events to the bus and reacting to them.

        Every cross-service read is therefore eventually consistent, and that
        is a decision rather than a limitation. Code that needs a synchronous
        answer from another service has a modelling problem, not a latency
        problem.

    constraints:
        - One service, one database, no shared schemas
        - Cross-service communication is events only
        - Every event is versioned and additive

    glossary:
        - Deployable — a service plus its database, released together
        - Projection — a read model built by consuming events

The last sentence of that overview — a modelling problem, not a latency problem — is the one earning its place. Stating that reads are eventually consistent only describes the system. Saying what it means when that bites you tells an agent what to do about it, which is the difference between one that reports "this read is stale, the data is on the wrong side of a boundary" and one that helpfully adds a synchronous call and is pleased with itself.

A concept that describes a constraint should say what to do when you hit it.

A command-line tool

export concept Toolchain:
    description: How the compiler and its tooling fit together.

    overview:
        Source becomes tokens becomes a tree becomes resolved output, in that
        order, with no stage skipped. Every stage is pure: same input, same
        output, no clock and no environment.

        Tooling never runs user code. Everything the tools report is derived by
        inspection, which is what makes the output safe to commit.

    constraints:
        - Stages never reach backwards
        - Nothing reads the clock, the environment, or the network
        - Identical input produces byte-identical output

Concepts below the root

These are the ones people under-write. Each of these spans several domains, is not a feature, and is not a rule — so it is a concept.

A core model

export concept DocumentModel:
    description: What a document is, and what may change it.

    overview:
        A document is an ordered tree of layers. A layer is either a group or a
        leaf holding geometry. Nothing else is a document — the selection, the
        viewport and the tool state all live outside it and are thrown away on
        close.

        Identity is by stable id, not by position. A layer keeps its id across
        reorder, group, ungroup and undo, which is what lets external references
        to it survive.

    invariants:
        - Ids are never reused, even after delete
        - The tree is always valid; there is no intermediate broken state a
          renderer could observe
---
name: document-model
description: "Concept: What a document is, and what may change it."
---

# DocumentModel

## Overview

A document is an ordered tree of layers. A layer is either a group or a leaf
holding geometry. Nothing else is a document — the selection, the viewport and
the tool state all live outside it and are thrown away on close.

Identity is by stable id, not by position. A layer keeps its id across reorder,
group, ungroup and undo, which is what lets external references to it survive.

## Invariants

- Ids are never reused, even after delete
- The tree is always valid; there is no intermediate broken state a renderer
  could observe

## Authority

This describes the product as it is meant to work. Where the implementation
disagrees, treat this as correct unless the task is explicitly to change it.

Rendering, Tools and Files all touch this and none of them owns it. It is not a component either — it is the shape of the data, which several components implement between them.

A cross-cutting mechanism

export concept UndoModel:
    description: How undo works, and what makes something undoable.

    overview:
        Every document mutation is expressed as a command with a forward and a
        reverse. Undo applies reverses in order; redo reapplies forwards. There
        is no snapshotting.

        A gesture is one command, not many. Dragging a layer three hundred
        pixels produces a single Move, coalesced as it happens, because a user
        thinks of it as one action.

    boundaries:
        - Anything outside the document is not undoable: selection, zoom, tool
          choice, panel layout.
        - Anything inside it always is. There is no such thing as a mutation we
          decided not to record.
---
name: undo-model
description: "Concept: How undo works, and what makes something undoable."
---

# UndoModel

## Overview

Every document mutation is expressed as a command with a forward and a reverse.
Undo applies reverses in order; redo reapplies forwards. There is no
snapshotting.

A gesture is one command, not many. Dragging a layer three hundred pixels
produces a single Move, coalesced as it happens, because a user thinks of it as
one action.

## Boundaries

- Anything outside the document is not undoable: selection, zoom, tool choice,
  panel layout.
- Anything inside it always is. There is no such thing as a mutation we decided
  not to record.

## Authority

This describes the product as it is meant to work. Where the implementation
disagrees, treat this as correct unless the task is explicitly to change it.

Undo shows up in Tools, in Files, in the UI. Explaining it once, here, beats explaining a third of it in three places.

A vocabulary that trips people up

export concept CoordinateSpaces:
    description: The three coordinate spaces, and when to convert.

    overview:
        Document space is unbounded and in points; the document's own geometry
        lives here. Canvas space is document space after zoom and pan. Screen
        space is canvas space in device pixels, including DPI scaling.

        Convert at the boundary, never in the middle. A function takes one space
        and says which in its name.

    examples:
        - `hitTest(pointInDocument)` — converted by the caller
        - `drawAt(pointInScreen)` — the renderer's edge
---
name: coordinate-spaces
description: "Concept: The three coordinate spaces, and when to convert."
---

# CoordinateSpaces

## Overview

Document space is unbounded and in points; the document's own geometry lives
here. Canvas space is document space after zoom and pan. Screen space is canvas
space in device pixels, including DPI scaling.

Convert at the boundary, never in the middle. A function takes one space and
says which in its name.

## Examples

- `hitTest(pointInDocument)` — converted by the caller
- `drawAt(pointInScreen)` — the renderer's edge

## Authority

This describes the product as it is meant to work. Where the implementation
disagrees, treat this as correct unless the task is explicitly to change it.

This one is pure "the thing everybody gets wrong in their first week". It is not a domain, not a component, not a feature, and it is not a naming rule — it is a model with three parts and a conversion discipline.

A backend example

export concept EventualConsistency:
    description: What "eventually consistent" means on this platform, concretely.

    overview:
        A write to one service is visible to others when they have consumed the
        resulting event, typically within a second and guaranteed within the
        retention window. There is no read-your-writes guarantee across a service
        boundary.

        Code that needs an immediate cross-service answer has a modelling
        problem: the data is on the wrong side of a boundary, or the boundary is
        wrong.

    consequences:
        - A UI that writes then immediately reads across services must render
          optimistically
        - Retries must be idempotent; delivery is at-least-once

Every service touches this. No service owns it.

It also overlaps the root Platform concept above, which says the same thing in one sentence — and that is the intended shape. The root concept states the rule so that anybody reading it once knows the system is eventually consistent on purpose. This one is loaded only when somebody is actually up against it, and spends four times as many words on what that means in practice. Root concepts state; the concepts below them explain.

Extra properties that earn their place

None of these are required. Add one when you have something true to put in it.

PropertyUse it for
constraintsrules that hold everywhere in the program
nonGoalsdecisions against things, with the reason
principleshow to break a tie when the constraints do not decide
glossarywords your team uses in a specific way
layoutthe top-level directory structure, when it is not obvious

Resist adding more than two or three. A concept that has become a table of contents has stopped being a concept.

concept, or something else?

Because concepts are no longer just the root one, the boundaries matter more.

If it isWrite a
an idea you would draw on a whiteboardconcept
an area you would assign work todomain
something a user can dofeature
a thing in the codebase with a namecomponent
a rule about how work is donereference

The two worth dwelling on:

concept or domain? A domain is where work happens; a concept is what you must understand. "Rendering" is a domain — you get assigned to it. "Coordinate Spaces" is a concept — you must understand it to work in Rendering, Tools and the UI. If it has an owner and a backlog, it is a domain.

concept or reference? A concept describes how something works. A reference states how work should be done. UndoModel is a concept: this is the machinery. Naming is a reference: this is the rule. The giveaway is voice — a concept is descriptive, a reference is prescriptive.

When not to write one

  • As a README. A README tells a person how to install and run the thing. A concept tells an agent how something is shaped. They overlap by about one paragraph.
  • For an area with a backlog. That is a domain.
  • For one module. That is a component.
  • Before you know. A vague concept is worse than none, because it is normative — an agent will treat "the system is modular and extensible" as a fact it must preserve, which means nothing and blocks everything.

The test

Read your concept and ask: could someone make a correct architectural decision from this alone, about a part of the codebase they have never seen?

If yes, it is doing its job. If it only makes sense to someone who already knows the system, it is a summary, and summaries are for people who do not need them.

How it renders

---
name: editor
description: "Concept: How the vector editor fits together."
---

# Editor

## Overview

A document is a tree of layers rendered onto a single canvas. Tools mutate the
document; the renderer never does. Every mutation goes through the command
stack, which is what makes undo total rather than best-effort.

## Constraints

- The renderer is pure: document in, pixels out
- No tool touches the canvas directly
- Every user-visible action is undoable

## Authority

This describes the product as it is meant to work. Where the implementation
disagrees, treat this as correct unless the task is explicitly to change it.

The root concept is usually worth naming in an agent's own context, rather than leaving it to arrive through a skill:

export agent BuildEditor:
    description: Implement changes in the editor.

    context:
        - Editor

    use:
        - ImplementRendering
        - ImplementTool

The other concepts are better attached to the skills that need them, so they load when they are relevant rather than always:

export skill ImplementTool:
    description: Add or change a drawing tool.
    when: Use when changing anything under the tools domain.

    context:
        - Tools
        - UndoModel
        - CoordinateSpaces

    prompt:
        Every completed gesture is one undo step.

The root concept is always in the room. Everything else arrives when it is needed.

On this page