Wiki
猫Table of Contents
- Framework Backlog
- Done
- [1.0.0]
- [1.0.0-rc.14] Webhook runtime model
- [1.0.0-rc.13] Observability Model
- [1.0.0-rc.13] Authorization and Policy Model
- [1.0.0-rc.13] User-Facing vs Internal Error Model
- [1.0.0-rc.13] Configuration Freeze Model
- [1.0.0-rc.13] Update Schema Contract
- [1.0.0-rc.12] Conversation / Scene Model
- [1.0.0-rc.12] Typed Handler Input Model
- [1.0.0-rc.12] Request Context / Cancellation Model
- Ideas
Framework Backlog
This page tracks framework-level backlog items that are about missing concepts in the library itself, not just missing documentation.
Done
[1.0.0]
Major — closed before 1.0.0 tag
- M1.
BotPayloadType*arevar, must beconst—bot.go:50-59. Public sentinels are user-mutable globals.KeyboardButtonStyle*inkeyboard.go:10-17already usesconst; match the pattern. - M2.
Observermethod naming asymmetry —observer.go:147-157.OnReceiveUpdate→OnUpdateReceived;OnHandledUpdate→OnUpdateHandledto matchUpdateReceivedEvent/UpdateHandledEventand the rest of theOnXpattern. Breaking after 1.0. - M3. Uploader returns ad-hoc error string instead of
*ResponseError—tgapi/uploader_api.go:183.tgapi/api.go:258-292returns*ResponseError; uploader must do the same soerrors.As(err, &tgapi.ResponseError{})works for upload paths too. - M4.
BotOptsFileJSONis missingPollTimeout—bot_opts_loader.go:35-46, plusFromBytes/ToBytesmapping. File round-trip silently dropsPollTimeout. - M5. Stale
Bot.Updatesgodoc —methods.go:11-44. Claims "30-second timeout" and "empty slice if none"; in reality timeout isbot.pollTimeoutand the function returnsnilon error. - M6. Self-contradicting
NewRandomDraftProvidergodoc —drafts.go:50-59. Says "cryptographically secure random numbers" but usesmath/rand/v2(the underlying generator type correctly notes it is not crypto-secure). - M7.
Draft.Deletegodoc says "internal method" —drafts.go:190-201. Method is exported; either rewrite the godoc with a public-intent description or unexport. - M8. Russian comments in production code —
msg_handler.go:28,tgapi/uploader_api.go:181. - M9.
MessageContext.Errorgodoc references unexported helper —msg_context.go:540. Rewrite to describe the centralized handler error path andIsUserErrorgating. - M10.
SceneandSceneSessionmix exported fields with setters —Scene.PluginNameunexported;SceneSession.Dataunexported, use accessor helpers. - M11. Constant-time compare for webhook secret —
bot_webhook.go. Usesubtle.ConstantTimeCompare.
Minor — closed before 1.0.0 tag
- Strip
// Internal helper …godoc from unexported funcs. Plugin.AddCommandgodoc references unexported field.command.Runnerbuilder:Onetime/Timeout→Every/Async;Once()removed.- Typo in webhook error string: "MaxConnections must between 1 and 100" → "must be between".
RunWebhookWithContextinlineerrors.New(...)→Err*sentinels.tgapi.UpdateTypeManagedBotmissing godoc.Bot.GetAPI,Bot.GetUploader,InlineKeyboard.GetMaxRowmissing godoc.Bot.L10ngodoc says "Returns empty string if translation not found"; actually returns the key.Bot.handlepanic recovery only logs — emitErrorEventso observers see panics.handleCallbackvshandleMessageplugin-logger assignment asymmetry — aligned.SetCallbackDatagodoc: zeroBotPayloadTypebehavior documented explicitly.commands.goemptycase CommandValueAny:merged withdefault.Bot.SetDebugdoes not callconfigMutable— noted in godoc.
Tests added after the fixes
BotOptsFileJSONround-trip forPollTimeout.- Uploader 4xx/429 surfaces
*tgapi.ResponseError. Bot.handlepanic → observer receivesErrorEvent.- Webhook
/statuswith wrongSecretTokenreturns 404, constant-time-compare smoke test. - Table-driven
parseCommandcases for/cmd@botnamestripping.
[1.0.0-rc.14] Webhook runtime model
Current state:
- The repository already exposes low-level Telegram webhook setup APIs through
tgapi, includingSetWebhook(...),DeleteWebhook(...),GetWebhookInfo(...), and uploader-based certificate upload support. - The framework now exposes first-class bot-level webhook runtime entry points through
RunWebhookWithContext(...)andRunWebhook(...). - Webhook delivery now uses the same internal update queue, worker pool, runner startup model, and single-use runtime contract as polling.
- The webhook runtime behavior, security model, and polling-transition requirements are now documented in the main docs and wiki.
Why this matters:
- Telegram webhook transport support at the API-client layer was not enough on its own; users still needed a framework-owned runtime path to make webhook delivery feel equivalent to polling.
- A first-class runtime mode keeps worker scheduling, lifecycle behavior, configuration rules, and shutdown semantics aligned across both ingress models.
- The framework also needs an explicit transition story when users move a deployment back from webhook delivery to polling.
What is now present:
BotWebhookOpts,NewBotWebhookOpts(), and fluent helpers for webhook-specific configuration.RunWebhookWithContext(...)andRunWebhook(...)as bot-owned runtime entry points.- Shared queued update dispatch, worker-pool delivery, runner startup, and single-use run semantics between polling and webhook modes.
- Default fallback from webhook
AllowedUpdatesto the bot-level update type configuration. - Request validation for webhook path shape and TLS file count before remote webhook setup.
- Explicit remote webhook teardown through
CloseWebhook()or low-leveltgapi.DeleteWebhook(...)when switching a deployment from webhook delivery back to polling. - Regression coverage for queue delivery, runner startup, single-use behavior, body-size rejection, path and TLS validation, and status-endpoint auth behavior.
Practical target:
- Treat webhook delivery as a first-class framework runtime mode rather than leaving it as only a low-level
tgapitransport primitive.
[1.0.0-rc.13] Observability Model
Current state:
- The framework now exposes a first-class
Observermodel with typed runtime events and a panic-safe dispatch path through the bot runtime. - Observer hooks now cover update receipt and completion, command and payload handlers, generic update handlers, scene handler lifecycle, scene transitions, policy checks, runner completion, polling retries, and centralized error routing.
- Instrumentation remains best-effort and does not introduce a second execution pipeline or require application data to be threaded through observer callbacks.
Why this matters:
- Logging alone is useful, but it does not create a stable runtime-facing observability contract for metrics, tracing adapters, or structured event collection.
- Framework-owned hooks make runtime behavior visible without forcing users to patch internal routing paths or duplicate instrumentation logic around commands, payloads, scenes, and polling.
- A typed event model preserves clarity in godoc and tests while keeping observer integration lightweight.
What is now present:
Observeras the public instrumentation interface.- Typed events for update, handler, scene, policy, runner, polling, and error flows.
- Safe event dispatch through
safeEmitEvent(...), including panic recovery. - Runtime emission across command, payload, generic update, and scene flows.
- Policy evaluation events through
PolicyCheckedEvent. - Runner and polling observability through
RunnerFinishedEvent,PollingRetryEvent, and observerErrorEvents. - Regression coverage for observer configuration, command and payload lifecycle events, scene lifecycle and transitions, update-handler errors, callback decode failures, runner completion, polling retries, and policy checks.
Practical target:
- Treat observability as a first-class framework capability instead of leaving instrumentation to ad-hoc logging and external wrappers.
[1.0.0-rc.13] Authorization and Policy Model
Current state:
- The framework now exposes
Policy[T]as a first-class reusable authorization rule that runs against the normalizedMessageContextand injected app data. - Policies integrate with the existing execution model through
RequirePolicy(...), so authorization stays on the middleware path instead of introducing a second routing pipeline. - Bot-level and plugin-level registration helpers now make policy usage explicit in configuration.
Why this matters:
- Access checks are a common requirement in Telegram bots, but ad-hoc middleware alone does not create a stable framework concept.
- Reusable policy helpers make chat-type restrictions, admin checks, and callback restrictions visible and composable instead of spreading them across handler internals.
- Keeping policy execution on the existing middleware path preserves the framework's current runtime model while still giving authorization its own language.
What is now present:
Policy[T]as the public authorization abstraction.RequirePolicy(...)for adapting policies into blocking middleware.Bot.UsePolicy(...)andPlugin.UsePolicy(...)for registration ergonomics.- Built-in Telegram-aware helpers such as
RequirePrivateChat(...),RequireGroupChat(...),RequireSupergroupChat(...),RequireChatAdmin(...),RequireChatCreator(...),RequireBotAdmin(...), andRequireCallbackFromUser(...). - Composition helpers
AllPolicies(...),AnyPolicy(...), andNotPolicy(...). - Extended
MessageContextnormalization forChatandChatID, plus regression coverage for policy and normalization behavior.
Practical target:
- Treat authorization rules as a reusable framework concept instead of leaving access control as only an ad-hoc middleware pattern.
[1.0.0-rc.13] User-Facing vs Internal Error Model
Current state:
- The framework still keeps one centralized handler error path, but it now supports explicit classification between user-visible and internal-only errors.
- Unclassified returned errors remain user-visible for backward compatibility.
- Internal-only failures can now be logged without automatically sending their raw text back to the user.
Why this matters:
- A single unified error path is useful, but not every failure should become a user reply.
- Infrastructure faults, invariant violations, and internal routing failures are often important for operators while being noisy or misleading for users.
- Without classification, framework users either leak internal errors into chat UX or are forced to bypass the centralized path entirely.
What is now present:
AsUserError(...)andAsInternalError(...)to classify returned handler errors explicitly.IsUserError(...)andIsInternalError(...)for framework-side inspection.- Updated centralized
MessageContext.Error(...)behavior that still logs all errors but suppresses the automatic user reply for internal-only failures. - Regression coverage for both message and callback flows.
Practical target:
- Keep the centralized handler error path intact while making user-facing and operator-facing failure handling meaningfully distinct.
[1.0.0-rc.13] Configuration Freeze Model
Current state:
- The framework now treats bot configuration as structurally complete once the first runtime entry point begins:
Run(),RunWithContext(...), orRunWebhookWithContext(...). - Late bot-level mutation attempts no longer partially apply after runtime startup.
- Plugin registration and runtime configuration boundaries are now documented and tested as explicit framework behavior.
Why this matters:
- Runtime mutation of prefixes, middleware, payload policy, localization, runners, or shared dependencies is difficult to reason about safely.
- The framework already had real commit points like
AddPlugins(...); this work makes those boundaries explicit instead of leaving them as implementation details.
What is now present:
- Bot-level configuration mutators now ignore late calls after runtime start.
- Lifecycle and configuration-freeze rules are documented as a first-class concept.
- Regression tests now lock down ignored late mutations.
Practical target:
- Make startup, runtime, and configuration ownership predictable enough to rely on as a stable framework contract.
[1.0.0-rc.13] Update Schema Contract
Current state:
- Update normalization already existed, but it is now described and tested as an explicit framework-level contract.
- Routing categories and
MessageContextpopulation guarantees are now treated as a first-class part of the public model.
Why this matters:
- Handler code needs to know which
MessageContextfields are safe to rely on for each update path. - Without a formal contract, update handling remains understandable only by reading implementation details.
What is now present:
- A documented routing model for command flow, payload flow, and generic update handlers.
- Explicit
MessageContextfield comments for update-backed, callback-backed, and message-backed contexts. - Table-driven regression coverage for the normalized update contract, including callback target semantics and non-command update flows.
Practical target:
- Make update routing and
MessageContextguarantees explicit enough to serve as a stable1.0public contract.
[1.0.0-rc.12] Conversation / Scene Model
Current state:
- The framework is strong at handling a single update through commands, payloads, middleware, and update handlers.
- It already has useful lower-level building blocks such as
MessageContext, drafts, payload routing, plugins, and update handlers. - It now provides an implemented initial scene model for long-lived user interaction flows: scenes can be registered in plugins, entered through
MessageContext, persisted throughSessionStore, and routed before normal command handling.
Why this matters:
- Many Telegram bots quickly move beyond isolated commands and need stateful multi-step flows.
- Real bots often need concepts like "wait for the user's next message", "user is currently on step 3 of 5", or "button press moves the user to the next scene state".
- Without a scene model, library users end up building their own mini-framework on top of Laniakea.
What is already present:
- Active-scene routing before normal command flow.
- Per-user, per-chat, and per-user-chat session scopes.
- Explicit scene entry and exit through
MessageContext. - Step handlers, scene-local commands, and
OnMessage(...). - In-memory session storage by default, plus the
SessionStoreinterface for custom persistence.
What is still missing or not yet settled:
- Scene-local payload routing.
- A clear decision on whether any additional public scene-inspection API is needed.
- Further extensions beyond the current message-driven scene model.
Current API direction:
Scene,SceneContext,SceneSession, andSessionStore.Plugin.NewScene(...)andPlugin.AddScene(...).MessageContext.EnterScene(...),EnterSceneStep(...), andExitScene(...).SceneContext.Stay(),Next(...),Exit(),Pass(),BindData(...), andSaveData(...).- Storage-backed per-user or per-chat state with a clean interface for custom persistence.
Important design constraints:
- This should be additive and optional.
- It should not replace plugins, commands, or handlers as the normal framework entry points.
- It should work with existing middleware and
MessageContextinstead of introducing a second incompatible execution model.
Practical target:
- Extend the current first-class scene model where it adds clear value.
- Keep both step-based forms and mode-based chat flows supported without forcing users to build custom routing layers around active sessions.
[1.0.0-rc.12] Typed Handler Input Model
Current state:
- Commands and payloads currently expose parsed text through
ctx.Textandctx.Args. CommandArgprovides basic argument validation and shape checks.- Handlers still do most non-trivial parsing manually.
Why this matters:
- As bots grow, handlers often start with repetitive
ctx.Argsparsing boilerplate. - Validation logic tends to spread across handlers instead of living in one predictable binding layer.
- The current model is simple and honest, but it does not help enough once commands become more structured.
What is missing:
- A first-class way to bind command or payload arguments into a typed Go value.
- A framework-level pattern for conversion errors and validation errors beyond raw string handling.
- A low-friction way to move from positional arguments to a structured input object.
Possible API direction:
- A lightweight binding API such as
ctx.BindArgs(&input). - Or explicit typed command registration such as
NewCommandTyped(...). - Positional mapping into structs, optional fields, basic conversion support, and integration with current validation flow.
- Unified binding and validation failures routed through the current centralized error path.
Example of the kind of user code this should enable:
type BanInput struct {
UserID int
Reason string
}
func ban(ctx *laniakea.MessageContext, db *App) error {
var input BanInput
if err := ctx.BindArgs(&input); err != nil {
return err
}
return db.Ban(input.UserID, input.Reason)
}
Important design constraints:
- Avoid a reflection-heavy, magical subsystem.
- Keep the current
ctx.Argsmodel as the minimal baseline. - Treat typed binding as an ergonomic layer on top of the current command model, not a replacement for it.
Practical target:
- Remove repetitive parsing boilerplate while preserving the framework's explicit, Go-like feel.
[1.0.0-rc.12] Request Context / Cancellation Model
Current state:
RunWithContext(...)andRunWebhookWithContext(...)control bot runtime lifecycle and graceful shutdown.tgapialready supports context-aware methods.- Regular handlers do not receive a first-class request-scoped
context.Context.
Why this matters:
- Handler business logic often needs cancellation-aware database calls, HTTP calls, or downstream service calls.
- The framework already has a good runtime cancellation story, but it does not flow naturally into user code inside handlers.
- In modern Go APIs,
context.Contextis a standard part of operational correctness.
What is missing:
- A clean request-scoped context that follows each update through handler execution.
- A standard way for application code to stop work when the bot is shutting down or the update processing context is canceled.
- A direct bridge between bot lifecycle control and service-layer cancellation.
Possible API direction:
- Prefer a non-breaking approach by exposing context through
MessageContext, for examplectx.Context(). - Build the context from the update-processing lifecycle so it is meaningful during graceful shutdown.
- Make it natural to pass that context into database methods, HTTP clients, and
tgapi.WithContext(...)calls.
Why this should probably not be a signature change:
- Changing handler signatures to accept
context.Contextdirectly would be a public breaking change. - A
MessageContextaccessor would preserve compatibility while still giving handlers an idiomatic Go cancellation path.
Practical target:
- Let handler code participate naturally in cancellation and graceful shutdown without forcing users to invent their own context plumbing.
Related pages:
Ideas
Items in this section are intentionally speculative. They may become real backlog work later, or they may remain design notes if the current framework model continues to be sufficient.
Service layer and dependency graph model
Current state:
SetAppData(...)already gives the framework a simple shared dependency model through the generic bot type parameter.- That model is intentionally lightweight and works well for bots that only need a small set of long-lived shared dependencies.
Why this is only an idea for now:
- The repository does not currently show strong evidence that a heavier service container or scoped dependency model is necessary.
- A framework-owned dependency graph would add API and lifecycle complexity, so it should only move into the active backlog if repeated real-world usage shows that
AppDatais not enough.
What this could mean later if it becomes necessary:
- A first-class service registry beyond a single shared
AppDatavalue. - Optional validation of required services before runtime startup.
- Clearer lifecycle or scoping rules for shared framework-managed services.
Plugin composition contract
Current state:
- Plugins are already more than a loose command bag: registration via
AddPlugins(...)snapshots plugin state, treats registration as a commit point, and documents post-registration mutation as unsupported. - This gives the framework a meaningful baseline contract around plugin ownership and immutability at runtime.
Why this is only an idea for now:
- The current repository state does not yet show strong pressure for a richer framework-level plugin dependency or capability model.
- Additional composition contracts would add API surface and validation rules, so they should remain optional design work until repeated real-world plugin interactions justify them.
What this could mean later if it becomes necessary:
- Explicit plugin dependencies.
- Shared capability declarations or requirements between plugins.
- A framework-level composition model for validating or coordinating plugin relationships.
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview