Service Library & Capability Surface (Proposal)¶
Status: draft proposal. Not part of the canonical numbered set. If accepted, the service-library posture folds into 02 System Architecture §3 (design principles) and §4–5 (stack/components); the capability-surface model extends 09 External API & Tool Integration §1; the decision record (ADR-028 below) is appended to the decision log. Do not run the docs index/changelog maintainer against this file until it is promoted. The ADR number is provisional until promotion (latest accepted is ADR-027).
Precis¶
Thinklio stands in a two-sided relationship with the wider tool ecosystem, and the two sides are mirror images of each other.
- It consumes. Agents do real work by calling out to capabilities that live outside the core: a Cliniko wrapper, a research pipeline, an accounting integration. These already exist as the
thinklio-servicesrepository — a versioned collection of stateless, independently-deployed services fronted by MCP/HTTP. This proposal names that posture and gives it rules. - It exposes. Thinklio is itself a capability that agents, other apps, and increasingly the operating system want to drive. Today that is the three API surfaces of doc 09. This proposal generalises them into a single capability surface, defined once and projected per protocol (MCP for the AI ecosystem, App Intents for Apple, App Actions/Gemini for Android, REST for direct integration) — the exact dual of the compose-once/render-per-channel delivery model (ADR-026).
The unifying invariant across both sides: every invocation — by a human, an agent, or the OS, over any protocol — is a governed principal passing through the harness. No protocol gets a side door around policy, credit, and audit.
This is not a turn to microservices. It is a deliberately small library of services around a Convex core, justified by reuse and by the statefulness mismatch between proxy work (stateless) and the core (reactive, durable, governed).
1. Background — consume and expose are the same shape¶
An agent turn that calls Cliniko and an OS that asks Thinklio to create a task are the same structural event seen from opposite ends: a principal invoking a schema'd capability under governance. Recognising this collapses what looks like two unrelated concerns (how we build integrations; how we build APIs) into one model with two directions.
This proposal does not change the agent loop or the chat core. It formalises the boundary at the edge of the core — what crosses it, in which direction, and under what contract.
2. The service library (the consume side)¶
thinklio-services is already a versioned monorepo of services with a declarative registry.yaml and shared CI. This is the canonical pattern, and it is endorsed, not merely tolerated.
What a service is. A self-contained, independently-deployed, independently-versioned capability with a stable contract (/mcp, /health, /meta), reachable by any authorised caller. Members fall into three rough shapes:
- Specific wrapper — a thin, faithful proxy over one third-party API (Cliniko, Xero, Notion). Stateless; credentials arrive per request; no Thinklio data.
- General capability — a broader pipeline that composes several upstreams (the research service: web + academic search, enrichment, fact-check). May be stateful internally (jobs, cost tracking) but owns its own state.
- Something else — a consumer that happens to live here because it is operationally adjacent (fallbot, the Telegram agent; the Cliniko mirror).
Why a library and not the main binary. Three reasons, in order of weight:
- Reuse across surfaces. fallbot already proves the point: a service designed cleanly is callable from more than the Thinklio web app — other Thinklio apps today, and outside callers eventually. Baking these into the main deployment would forfeit that.
- Independent lifecycle. A Cliniko API change should redeploy the Cliniko service, not the platform. Versioning and blast radius are per-service.
- Independent scale. Stateless services scale horizontally on demand; the reactive core does not need to.
Why this is not microservice sprawl. The library is small, curated, and registry-tracked. Services are extracted on evidence of reuse or a genuine statefulness/lifecycle boundary, not by default. The core stays a monolith (one Convex project, doc 02 §3.3); only genuinely peripheral or independently-useful capabilities live outside it.
3. The substrate rule — Go vs Convex¶
The library is polyglot by design, and the choice of runtime follows a single test: does the service own reactive, durable, governed Thinklio state?
| If the service is… | Runtime | Examples |
|---|---|---|
| A stateless proxy to third-party state (no Thinklio data, no durable state of its own) | Go (thinklio-services, mark3labs/mcp-go) |
Cliniko, Xero, Notion, research |
| A stateful Thinklio capability (reactive data, vector search, durable workflows, governed atoms) | Convex (own deployment + functions, MCP/HTTP facade) | Memory (see Memory as a Service) |
Both kinds are equal members of the library and obey the same contract (stable MCP/API, independent deploy, registry entry). They differ only in runtime, because rebuilding a stateful, vectorised, governed store in Go/Postgres would re-implement the very stack (pgvector, RAG, Workflow, in-process governance) the Convex-first rebuild consolidated (ADR-017). Statelessness is what makes Go the right tool for proxies; statefulness is what makes Convex the right tool for memory.
4. Relationship to ADR-017 (the Convex-first rebuild)¶
This proposal does not re-open ADR-017. That decision retired the Go application core — the event bus, the durable-execution engine, the system-of-record plane — and folded them into Convex. It never prohibited external services; thinklio-services exists and is blessed. "A versioned library of services around the core" is a topology choice about peripheral capabilities, fully compatible with a Convex-first core. The line ADR-017 drew still holds: the core is one Convex project; everything in the library is peripheral and reachable only across a stable contract.
5. The capability surface (the expose side)¶
Doc 09 defines three API surfaces (Channel, Platform, Integration) that already "resolve to the same authorisation model and governance framework." This proposal names the principle underneath them and extends it to non-API consumers:
Define each capability once, as a governed, schema'd unit in the platform; project it into whatever protocol a consumer speaks.
This is the dual, on the capability axis, of the delivery model's compose-once, render-per-channel (ADR-026) on the message axis:
| Axis | Define once | Project per |
|---|---|---|
| Outbound message | Semantic message | Channel (web, Telegram, email) — ADR-026 |
| Capability | Capability definition (params, returns, trust) | Protocol (MCP, App Intents, App Actions, REST) — this proposal |
A capability is described once — the same manifest doc 02 §3.9 ("unified capability model") already uses for tools and agents — and adapters project it: an MCP tool definition for AI clients, an App Intent for Apple, a REST endpoint for the Integration API. The platform holds the single source of truth; the protocol adapters are thin and own no logic.
6. The OS as a consumer (future seam)¶
Apple's direction (App Intents, assistant schema exposure, Siri acting on an app's behalf) signals a shift: an app's ultimate I/O is no longer always a human. Increasingly the operating system is a caller — it wants a schema of what the app can do, not a chat box.
That arrives on the capability surface, not the conversational one. App Intents is Apple's proprietary dialect of "expose your capabilities as a callable, schema'd surface"; MCP is the cross-vendor dialect; the Integration API is a third. They are the same idea in three protocols, which is exactly why the define-once/project-per-protocol model absorbs the OS for free.
Two rules keep it clean:
- The platform defines; the native layer projects. App Intents exposure is native platform work (iOS
AppIntents, surfaced from the planned Flutter app via platform channels). The intent definitions derive from the platform's capability manifests; the native layer must not fork the capability model. - Broaden "channel/consumer" now. A channel/consumer is any bound consumer of Thinklio — human, agent, or OS — across either the conversational or the capability surface, not "a place a human types." Defined broadly today (a cheap edit), the OS is a first-class citizen when the platform direction matures; defined narrowly, it is a retrofit. This refines 16 Chats, Channels & Identity §3.
This is a seam to document, not build: it lands in the mobile-app era and asks nothing of the current phase beyond the broadened definition.
7. Every invocation is a governed principal¶
The safety property that makes all of this tractable: regardless of which protocol carries it, an invocation resolves to a principal (user / account / agent — the polymorphic identity of ADR-025) and passes through the same harness — policy → credit → audit → durable execution. Siri invoking "on behalf of Andrew" is a principal with Andrew's grants. An outside app calling over MCP is a principal whose grants were issued at registration. The capability surface does not add a governance model; it reuses the one the harness already enforces. This is the same uniformity doc 09 §1.4 already asserts for the three API surfaces — generalised to every protocol.
8. What is cheap now vs deferred¶
Cheap now (do, or preserve):
- Keep
thinklio-servicesregistry-tracked and contract-stable (already true). - Adopt the substrate rule (§3) as the test for any new service.
- Broaden the "channel/consumer" definition (§6) in doc 16 / doc 02.
- Keep capability definitions in one manifest format (doc 02 §3.9 already does).
Deferred (document the seam, do not build):
- App Intents / OS exposure (native, mobile-era).
- Outside-caller (third-party) access to the capability surface at large.
- A live service-registry aggregator (already on the
thinklio-servicesroadmap, docs 54).
9. Draft decision record (draft ADR-028)¶
To be appended to decision-log.md on acceptance. Number provisional.
ADR-028: Service Library & Capability Surface¶
Date: 2026-06-15 Status: Proposed
Context: Thinklio both consumes external capabilities (already as the thinklio-services library) and is consumed as a capability by agents, other apps, and — increasingly — the operating system (App Intents/Siri). These were treated as unrelated concerns (integrations vs APIs) and the OS-as-consumer case had no home in the model.
Decision: Adopt two complementary postures. (1) A versioned service library of stateless, independently-deployed, MCP/HTTP-fronted services around the Convex core, polyglot by a substrate rule — Go for stateless third-party proxies, Convex for stateful Thinklio capabilities. (2) A single capability surface for Thinklio's own capabilities, defined once as governed schema'd units and projected per protocol (MCP, App Intents, App Actions, REST) — the dual of compose-once/render-per-channel. Every invocation across any protocol is a governed principal through the harness. Broaden the definition of "channel/consumer" to include the OS.
Reasoning: Reuse (fallbot already consumes services cross-app), independent lifecycle and scale justify the library; the statefulness mismatch justifies the substrate rule without re-opening ADR-017. Define-once/project-per-protocol prevents the OS from forking the capability model and reuses the existing governance harness rather than inventing a parallel one.
Implications:
- The substrate rule governs all future service-extraction decisions.
- The capability manifest (doc 02 §3.9) becomes the single source projected by per-protocol adapters.
- "Channel/consumer" is redefined broadly (doc 16 §3 refinement).
- App Intents / OS exposure is an explicit deferred seam, native-layer work in the mobile era.
- Memory is the first Convex-substrate member of the library (separate proposal).
10. Open questions¶
- Where the MCP facade for a Convex-substrate service lives — a Convex
httpActionhosting the streamable-HTTP transport directly, or a thin separate facade. Prototype both for memory. - Registry unification — does the Convex-substrate memory service register in the same
registry.yamlas the Go services, or a parallel registry? One registry is simpler if the schema tolerates the substrate field. - Outside-caller trust model — when third parties consume the capability surface, registration/approval and grant scoping need the same rigour as the Integration API (doc 09 §1.3); confirm they share one model.
- App Intents projection mechanics — whether Flutter platform channels can carry App Intents cleanly, or whether a thin native module is required. A mobile-era spike.
11. Revision history¶
| Date | Change |
|---|---|
| 2026-06-15 | Initial draft proposal. |