The Shared Core
The shared domain layer that every Holons interface calls into
@holons/core is the heart of the Holons codebase. It owns every piece of domain logicβscoring, tasks, council, DNA, federation, expenses, calendar, users, library, checklists, REA, settings, categories, commandsβand exposes them as independently-callable modules. Every interface in Harvest (web, Telegram, CLI, AI, MCP) calls these modules. They do not duplicate the logic; they consume it.
This page is the architectural overview. For domain detail, jump to the individual pages.
What "shared core" means in practice
One source of truth for Holons domain logic, several UIs sharing it.
A user creating a task in Telegram and a Claude agent calling the MCP task_create tool reach the same function: createDefaultTask in packages/core/src/tasks/creation.ts. The Quest they produce lives in the same lens in HoloSphere. Scoring sees the same event when the Quest completes. Federation propagates the same data.
This is why "compute user score" means the same thing in every UI: there is only one implementation of it.
Domain catalog
Each domain lives at packages/core/src/<domain>/, exposes its public API via index.ts, and is reachable from any consumer with a subpath import.
federation
Cross-holon publishing via HoloSphere + Nostr
(no page yet)
expenses
Shared cost logging and splitting
(no page yet)
calendar
Events, recurring events, scheduling
(no page yet)
users
Multi-holon membership and appreciation tracking
(no page yet)
checklists
Recurring and role-based task lists
(no page yet)
shopping
Shared shopping lists
(no page yet)
settings
Per-holon configuration with hex-grid integration
(no page yet)
categories
Taxonomies for items and contributions
(no page yet)
The "no page yet" entries are real, working domains; they just don't yet have dedicated docs. The MCP server exposes tools for every one of them.
Conventions
A small set of rules keeps the core stable as it grows.
Subpath imports β no central barrel
There is no import { everything } from '@holons/core' barrel that re-exports every domain. The subpath form is mandatory because:
It makes domain dependencies explicit in import lines.
Tree-shaking works without any bundler magic.
New domains land without touching a central re-export file.
Two domains can have a function of the same name without colliding.
Subpath exports are configured via a wildcard in packages/core/package.json:
Creating packages/core/src/<new-domain>/index.ts instantly makes @holons/core/<new-domain> importableβno configuration change needed.
TypeScript everywhere
Core is TypeScript. New UIs are TypeScript. The Telegram bot is in a mixed JS+TS state (bootstrap and several modules migrated; the rest still JS, allowed via allowJs). Types are exported alongside functions:
UI-agnostic
Core never imports svelte, telegraf, or any framework. Only import type for shared interface shapes when truly necessary. The list of allowed runtime dependencies is intentionally tiny: holosphere, ical.js. That's it.
Pure vs. impure split
Every domain has the same internal structure:
Pure helpers β functions that take data and return data. No I/O, no side effects. Used by every UI for live rendering (live tallies, live scores, live previews).
Impure helpers β functions that read from or write to HoloSphere. Called when the action is committed.
This split is what lets the web dashboard show "if you complete this task, you and Laura each get 2 points" without round-tripping to storage: the same pure function runs in the browser.
One HoloSphere factory
There is exactly one place that instantiates HoloSphere: @holons/core/holosphere. Every UI calls into it for identity-aware writes (writeWithIdentity, canWriteToHolon). This keeps actor resolution, write permissions, and audit attribution consistent regardless of where a call originates.
Tests live next to the code
Every domain ships a vitest spec at packages/core/src/<domain>/<domain>.test.ts. Tests are not in a separate tests/ tree. This keeps each domain self-contained and makes it obvious which test exercises which file.
How a new domain lands
Create
packages/core/src/<domain>/{index.ts, types.ts, β¦}.Export the public surface from
index.ts.Write a vitest spec:
<domain>/<domain>.test.ts.If the domain has its own MCP tool wrappers, add
packages/mcp-ui/src/tools/<domain>.tsand append the domain name to theDOMAINSarray inpackages/mcp-ui/src/tools/index.ts.
That's it. No central registration, no manifest, no per-UI plumbing. Every UI that imports from the new path automatically gets the new capability.
How a new UI lands
mkdir packages/<my-ui>/src.Copy
packages/text-ui/{package.json,tsconfig.json}as a starting point.Depend on
@holons/corevia"@holons/core": "workspace:*".pnpm installfrom the repo root.Implement the renderer / parser / input mode against
@holons/core/commandsso the new UI invokes the same actions as the others.
The shared command registry is the single integration point a new UI needs to touch. Every existing command becomes available to the new UI for free.
What this architecture buys
Consistency. Every interface produces the same data, scores it the same way, federates the same events.
Speed. A new feature is one PR, not five.
Independence. Each UI can iterate on its presentation without coordinating with the others.
Testability. Domain logic is exercised by vitest specs that run in milliseconds, with no UI launched.
AI-readiness. Because every operation is a
CoreCommandwith declared params, exposing it as an MCP tool or Claude tool is a one-line wrapper.
See also
Harvest β the monorepo that contains
@holons/coreand its UIsMCP Server β the auto-registered tool surface over the core
Last updated
Was this helpful?