Extend the platform
You extend the platform by implementing a port — the ports are the extension surface. Ports live in packages/platform-domain/**/ports/ (infrastructure-free); your adapter lives in apps/api/**/adapters/.
Decide generic vs consumer-specific
Section titled “Decide generic vs consumer-specific”- Generic (any future consumer would want it) → goes in
packages/platform-ingestion/. Example: a new source reader, a new port. - Consumer-specific (only one app cares) → goes in the consumer’s directory. Example: classifieds-listing parsing, bookmark-tag derivation.
Add a pipeline step
Section titled “Add a pipeline step”Implement IWorkflowStep (or extend BaseWorkflowStep) and register it in the StepRegistry. Most steps register unconditionally and resolve their credential (an LLM, Gmail, Notion, … Channel) per run, failing clearly at that point when the owning user has no connected channel of that scheme — see Steps for the current registration model.
Add a dispatch destination
Section titled “Add a dispatch destination”Implement IDispatchTarget (packages/platform-domain/src/dispatch/ports/IDispatchTarget.ts) and add its scheme to DESTINATION_SCHEMES (packages/platform-domain/src/connector/Destination.ts — the canonical vocabulary shared by presets and buildJobs). The dispatch step (DispatchStep) routes by config.destination’s URI scheme, threading context.metadata.userId/orgId into the destination config for owner-scoping.
Add an ingestion source
Section titled “Add an ingestion source”Implement ISourceReader in packages/platform-ingestion/. The consumer seam is IContentHandler — a consumer app implements it to translate BaseContent into its own entity.
Add an LLM provider
Section titled “Add an LLM provider”Implement ILlmClient. The enrich / analyze steps call the LLM through this provider-agnostic port (Cline / Anthropic / OpenRouter today), so adding a provider is an adapter, not a code change in the steps.
Conventions
Section titled “Conventions”- TypeScript + Bun; checks are
bun run typecheck/bun run lint/bun run test:unit. - Keep the domain package infrastructure-free (no
drizzle-orm,pg-boss,fs,http). - Import from the
@abeauvois/platform-domainbarrel, not deep relative paths.
