Extension tenancy ADR + capability contract (ADR 0006) - #140
Draft
geminixiang wants to merge 5 commits into
Draft
geminixiang wants to merge 5 commits into
geminixiang wants to merge 5 commits into
Conversation
…(ADR 0006) Extensions declare host capabilities in package.json mikan.requires; the loader checks declarations against the injected services before importing the module, so a missing capability is one clear activation error instead of a runtime throw at the first api call. api.capabilities.has/list lets extensions degrade gracefully without typeof-probing. The capability→ service rule has one home (CAPABILITY_PROBES); validate reports declared requires and warns on unknown names (activation hard-fails them). deploy/examples/extensions/scheduled-counter is the golden-path example — one command, one callback schedule, per-conversation state — and mikan ext init scaffolds the same shape ready for ext dev. ADR 0006 records the tenancy model (actors, scopes, deferred stages); research reviews archived under docs/research/design-review-2026-08/.
…-off Three independent implementations (A: this branch; B/C: parallel agents in worktrees) were built from one spec and compared. A won on gate compliance (B tripped knip, C shipped a type error in its example and a stale public-api snapshot), but B/C converged on a better naming axis: capability names should follow the api surface an author calls (api.notify → messaging.notify, api.react → reactions), not host service field semantics. Adopted. Comparison recorded in docs/research/design-review-2026-08/extension-capability-impl-comparison.md.
…gent Record the motivating principle behind the extension system: the agent's tools are deliberately confined, and without a sanctioned outlet users drill through the wall — the real case being a raw Slack API key placed in a conversation vault so bash could curl the platform API, putting the credential inside the model's reach. Extensions hold the means in host code and expose only semantic surfaces; the model requests effects, never credentials. This also grounds why extension code runs on the host, and gives review its one-question heuristic.
Four independent agent perspectives — enterprise user advocate, extreme skeptic, platform-ecosystem historian, agent-capability architect — argue whether the extension system earns its keep. They converge unexpectedly: in-process is deployment-trusted code and must be named as such; the synchronous policy seam (pre-execution veto) is the one irreplaceable core even the skeptic concedes; and the right ambition is an internal application SDK for 5-20 audited extensions, not a marketplace. The synthesis records the skeptic's routing test (when NOT to write an extension), the user-demand gap map (platform events, identity queries, portable interactions), and the architect's four control-plane gaps (execution context, policy decisions, durable ops, credential handles) — all inputs for the pending ADR 0006 revision.
geminixiang
added a commit
that referenced
this pull request
Aug 26, 2026
The extension route is frozen (PR #140 stays a draft): for 'let the agent use service X', MCP is the cheaper boundary — servers are separate processes speaking an external standard, so mikan carries no API-surface maintenance and inherits the existing server ecosystem. The motivating case stays the same as ADR 0006's: a user who wants Slack/API access should configure a server holding the credential, not put a raw key where the model can reach it. - src/mcp: loadMcpTools connects settings-declared servers (stdio or streamable HTTP), wraps their tools as AgentTools named mcp__<server>__<tool>, and reports per-server failures without failing the rest; dispose closes clients with the runner. - settings: mcpServers maps merge per server name across global and conversation scopes (conversation wins; disabled: true turns one off); a change refreshes cached runners like a model switch. - admin portal: MCP panels on both tabs — add (stdio command or URL, env/header credentials), enable/disable, remove; the list endpoint redacts env/header values to key names. - deps: @modelcontextprotocol/sdk ^1.30.0. Tests spawn a real stdio server end-to-end (echo tool, env-passed credential, isError-to-throw contract, unreachable-server isolation).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
First shippable slice of the extension tenancy work — the analysis, the decision record, and the capability contract, leaving activation semantics untouched.
package.jsonmikan.requiresdeclares needed host capabilities; the loader checks against injectedExtensionHostServicesbefore importing the module — a miss is one clear activation error, not a runtime throw at the firstapi.*call.api.capabilities.has/listreplacestypeof api.schedules?.onCallbackprobing. Capability→service mapping has one home (CAPABILITY_PROBESin loader.ts). Names follow the api surface an author calls (messaging.notify,reactions), not host field names.mikan ext validateprints declared requires and warns on unknown names; activation hard-fails them.deploy/examples/extensions/scheduled-counter(one command + one callback schedule + per-conversation state, README explains the per-conversation activation model) andmikan ext init <name>scaffolds the same shape, immediately runnable viaext dev.How it was built
Three independent implementations from one spec (this branch + two parallel agents in worktrees), then compared: this version won on gate compliance; the parallel versions' author-facing capability naming was adopted. Full comparison and research reviews under
docs/research/design-review-2026-08/.Not in this PR (deliberately)
api.stateprimitive (stage 2), no secrets migration (stage 3), no service activations (stage 4).requires— rides the next touch of that example.Verification
Full gate green: lint, fmt:check, knip, build, 124 files / 1761 tests.
ext init→ext validateexercised end-to-end in tests and manually.