Skip to content

Classes and instances

Quayside has a deliberate three-layer identity model. Each layer answers a different question.

Class

A class is the durable, governable “kind of agent” — eng/code-reviewer, support/tier1-bot, finance/expense-classifier. It is what your registry contains, what policies attach to, and what audit aggregates against.

A class has:

FieldNotes
slugURL-safe identifier, must be unique. Lower-case, hyphens, slashes. eng/code-reviewer is fine.
nameHuman-readable. Shown in the dashboard.
purposeWhat it does, in a sentence. Used at registration for human review and surfaced in audit.
owner_principal_idA real human, not a team alias. Accountability lives here.
lifecycle_statusOne of draft / active / deprecated / sunset / external.
supersedesOptional reference to another class id this one replaces.

There are tens to low-hundreds of classes in a real organisation — not thousands.

Lifecycle

draft ──approve──▶ active ─┬─► deprecated ──► sunset (terminal)
└─► sunset (emergency skip)
external ──promote──▶ active (shadow → real)
external ──reject──▶ sunset (shadow → terminal)
  • draft — registered, not yet active. The proxy refuses calls from a draft class. Use this if your organisation wants a two-step approval flow.
  • active — the normal state. The proxy accepts calls.
  • deprecated — the proxy still accepts calls, but the dashboard surfaces deprecated classes for migration. Use this when you want to phase a class out without breaking existing callers.
  • sunset — the class is rejected. Audit data remains.
  • external — shadow-discovery state. A class id that the proxy saw via the shadow log but was never explicitly registered. Promote or reject it from the dashboard.

The transition matrix is enforced by the service. sunset is terminal — you cannot un-sunset a class.

Instance

An instance is the runtime identity of a specific (class, principal) pair. Same class slug + same principal id always resolves to the same instance. Restart, crash, machine swap — same instance id.

The proxy claims instances automatically on first call. No CRUD surface — they exist as a side effect of using the system.

The schema is small:

FieldNotes
idUUID
class_idFK to the class
principal_idWho is calling on behalf of the class
first_seen_atWhen the (class, principal) pair was first observed
last_seen_atUpdated on every call

Instances are where you go to answer “who has actually used this class?”

Session

A session corresponds to one audit run — one trip through the proxy. The current schema doesn’t store sessions as a top-level table; the runs table is the audit-side projection.

A run carries:

  • the instance_id of who made the call
  • the started_at / finished_at timestamps
  • the final_effect (Allow, Flag, Block, …)
  • every step (one per detector inspection) attached via run_id
  • the token usage row attached via run_id

How they compose

Policy ────► attaches to Class
Instance (auto-claimed for each user × class)
Run / steps / token usage (one set per call)

Class is what humans care about. Instance is what the proxy tracks. Run is what audit reports against.

Why the model is shaped this way

You want policy attached to “the kind of bot” — not to each user. You want fleet questions answered at the class level. You want per-user usage answered at the instance level. You want per-call records answered at the run level. Three different aggregation grains, three different layers.