YouBothAgent▾
You — Business rules and flows you own. Read these yourself.
Both — Know the idea; your agent follows the details.
Agent — Conventions and references your agent follows. Look up as needed.
Workspace▾
App & Library▾
Domain▾
Scalar▾
Service Module Overview
Signing a token, streaming a stored file back to a browser, running an OAuth handshake to its end: none of these is a record. A folder built around a stored model would give you five files to leave empty and one to fill.
A service module is that folder without the model.
Where It Lives
In
lib/_<service>, with a leading underscore. The files inside drop it.libs/util/lib/_security/security.service.tsWhat It Owns
An action or a capability instead of a table. Nothing to list, edit, or keep until tomorrow.
sign · encrypt · stream · authorizeWhat It Leaves Out
No document file, no filters, no slices, no generated CRUD: there is no table behind it.
no *.document.ts · no slice()How It Is Called
The same path a model module uses, minus the document layer.
fetch → signal → service → srvkit/Here is one call through a service module, using
_oauth as the example:One call through a service module
A page, a store, an MCP client
The runtime itselfcron · process · initialize
oauth.signal.tsendpoint · internal
oauth.service.tsthe workflow
Another module's serviceservice<srv.UserService>()
An adapter in srvkit/plug() · use()
The outside world
A page, a store, an MCP client
The runtime itselfcron · process · initialize
fetch.listOAuthConnections()
oauth.signal.tsendpoint · internal
oauth.service.tsthe workflow
Another module's serviceservice<srv.UserService>()
An adapter in srvkit/plug() · use()
The outside world
- Two ways in. A caller reaches an
Endpointthroughfetch, and the runtime fires anInternalon a schedule, a queue job, or startup. - The service does the work. It asks another module through
service<srv.X>()and the outside world through asrvkit/adapter.
The Eight That Exist
This workspace has eight service modules, and reading them is faster than reading a description. They range from a server-only primitive to a whole authorization server, plus the empty root container each app and lib carries.
ModuleDescription
_security
JWT signing and verification, AES encryption, refresh-token minting. Server-only: no store, no UI.
_oauth
The OAuth 2.1 authorization server that issues the tokens
/mcp accepts._doc
Serves the Akan.js docs to agents over MCP. It reads a generated folder and writes nothing.
_localFile
Streams a public blob back as an HTTP
Response from a custom path. Four files, one endpoint._util_shared
A library's root container: an empty batch service and a client store other modules share.
_akan_minimal
An app's root container.
_akan is still the empty scaffold; _minimal adds four bench endpoints.Only four files are in every one of them. Here is which of the eight carry the optional ones:
Module
store
*.store.ts
test
*.test.ts
Util
*.Util.tsx
Zone
*.Zone.tsx
Feature modules
_security
✓
_oauth
✓
_doc
✓
Tests its service:
doc.service.test.ts._localFile
Root containers
_util
✓
_shared
✓
_akan
✓
The store is the empty scaffold.
_minimal
✓
The store is the empty scaffold.
✓Has the fileNo file
Not one of the eight has a Util or a Zone. That is not an accident of this workspace; the two UI pages explain why the files are rare and what goes there instead.
The Two Poles
Put a small feature module,
_security, beside the largest, _oauth: both have the same five kinds of file. What changes is how much each file holds:_security · The FloorIts service holds two secrets and hands back signed or encrypted strings. Nothing on screen renders it, so there is no store and no component.
- abstract.md
- What it owns, and four rules
- dictionary.ts
- Endpoint labels
- service.ts
- About 75 lines holding two secrets
- signal.ts
- One mutation,
encrypt - signal.test.ts
- Boots the barrel and calls it
_oauth · The CeilingA whole authorization server, and still no store: every screen it needs is a route in
libs/shared/page/oauth, not a section of another screen.- abstract.md
- Eight rules and a workflow chain
- dictionary.ts
- Labels in
.endpoint(), error keys in.error(), consent-page phrases in.translate() - service.ts
- About 500 lines: PKCE, rotation, revocation
- signal.ts
- 10 endpoints, 5 of them at the origin root
- signal.test.ts
- The protocol, end to end


A service module with state does not grow a table for it.
_oauth keeps every client, request and grant in memory(Map, { of: cnst.OauthGrant }) caches, and each shape is a scalar under libs/shared/lib/__scalar/. A scalar travels as JSON text, so the same declaration round-trips through the Redis and sqlite caches unchanged.Service File Map
Four files are always there. The rest arrive when the feature earns them, and both lists follow the order of this section's pages.
Always There
FileDescription
<service>.abstract.md
A title, one sentence on what it owns, and
## Rules: invariants the code cannot show.<service>.dictionary.ts
Built with
serviceDictionary: endpoint labels, error keys and UI phrases.<service>.service.ts
The workflow itself, built with
serve() naming the module, even when the body is empty.<service>.signal.ts
Two classes,
<X>Internal and <X>Endpoint. No Slice, because there is no table to page through.Only When Needed
FileDescription
<service>.store.ts
Only when the feature has client state. Four of the eight have one; two are empty scaffolds.
<service>.signal.test.ts
Boots the barrel and calls the endpoints through
fetch. _security and _oauth have one.<Service>.Util.tsx<Service>.Zone.tsx
Rare: none of the eight has one. The two UI pages of this section explain why.
Ship The Empty Files
The rule that most often looks like a mistake: a scaffold file stays in the tree even when it holds nothing. Here is
libs/util/lib/_util/util.signal.ts in full, unedited:libs/util/lib/_util/util.signal.ts
- Two exported classes, zero methods. That is the whole file, and it stays.
- Deleting it does not shrink the workspace, it changes it. The next developer first has to decide where an endpoint goes, instead of where it goes in the file already open.
- The first endpoint stays a one-line diff. Without the file, it would be a new file.
The Empty Forms You Will Meet
FileDescription
signal.ts
The builder callback returns an empty object, not nothing.
service.ts
A root container with no methods still declares its service.
store.ts
Exactly two comments,
// state and // action, mark where each half goes.apps/akan/lib/_akan is in exactly this state: its service, signal and store are all empty. apps/minimal/lib/_minimal keeps the same empty store beside its bench endpoints, and neither is waiting to be cleaned up.Model Module Or Service Module
One question decides it: is there a row you would want to list, filter, and still find next week?
- Yes: a model module at
lib/<model>. The service module you were about to write is one of its service methods. - No: a service module at
lib/_<service>.
Model Module
lib/<model>A stored table with a document file, filters, slices, generated CRUD and the five UI roles.
user · file · banner · notificationService Module
lib/_<service>No table, no document file, no slice. An action, a protocol, an integration, or a library's own root.
security · oauth · localFile · docScalar Module
lib/__scalar/<scalar>A value embedded in something else and never stored on its own. A service module's state takes this shape.
oauthClient · oauthGrant · oauthRequestThe next page is the abstract file, where the rules you just decided on are written down. After that the pages follow the call path: