backlog_wakeup
Get a dense session-start briefing of active tasks, epics, recent completions with evidence, and activity. Optionally scope to a folder, milestone, or epic for project-focused context.
Instructions
Dense session-start briefing: active tasks, current epics, recent completions (with evidence snippets), and recent activity. No focal entity required — use this at the start of every session to understand what you were working on. Optional scope narrows to a folder (for project-scoped briefing), milestone, or epic.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| scope | No | Optional entity ID to scope the briefing to a subtree. Must be a container (folder/milestone/epic). Use a folder ID for project-scoped wake-up (e.g. "FLDR-0001"). Omit to get everything across the whole backlog. | |
| max_completions | No | Max done tasks in the "recent" section. Default: 5. | |
| max_activity | No | Max recent activity-log entries. Default: 5. | |
| evidence_snippet_chars | No | Max chars of evidence to include per completion. Default: 160. |
Implementation Reference
- Core wakeup function — the main business logic for backlog_wakeup. Builds a briefing with identity (L0), active tasks and current epics (L1), recent completions and activity (L2). Supports optional scope filtering to a folder/milestone/epic subtree.
export async function wakeup( service: IBacklogService, params: WakeupParams = {}, ): Promise<WakeupResult> { const maxCompletions = params.maxCompletions ?? 5; const maxActivity = params.maxActivity ?? 5; const snippetChars = params.evidenceSnippetChars ?? 160; const identity = params.readIdentity?.(); // Scope validation + descendant set. // Scope is validated at the boundary — see assertValidScope + the // WakeupParams.scope JSDoc in ./types.ts for the reasoning behind // string-over-brand here. let scopeFilter: ((id: string) => boolean) | null = null; if (params.scope !== undefined) { assertValidScope(params.scope); const set = await descendantSet(service, params.scope); // Exclude the scope entity itself from result sections — the caller // asked for "what's inside this folder", not "this folder". set.delete(params.scope); scopeFilter = (id: string) => set.has(id); } const inScope = <T extends { id: string }>(xs: T[]): T[] => scopeFilter ? xs.filter(e => scopeFilter!(e.id)) : xs; // L1 Now — active tasks (in_progress | blocked), epics excluded; // and current epics as their own section. const active = await service.list({ status: ['in_progress', 'blocked'] }); const activeTasks = inScope(active.filter(e => e.type !== 'epic')) .sort(byUpdatedAtDesc) .map(toSummary); const epics = await service.list({ type: EntityType.Epic, status: ['open', 'in_progress'] }); const currentEpics = inScope(epics).sort(byUpdatedAtDesc).map(toSummary); // L2 Recent — last N done tasks by updated_at, + last N ops from the log. const done = await service.list({ status: ['done'] }); const completions = inScope(done) .sort(byUpdatedAtDesc) .slice(0, maxCompletions) .map(e => toCompletion(e, snippetChars)); // Pull extra activity entries when scoped — we filter client-side and // want to end up with at least maxActivity after filtering when possible. // 5x over-fetch covers the common "most ops touched other scopes" case // without unbounded cost. const opLimit = scopeFilter ? maxActivity * 5 : maxActivity; const ops = params.readOperations ? params.readOperations({ limit: opLimit }) : []; const filteredOps = scopeFilter ? ops.filter(op => { const eid = opEntityId(op); return eid !== undefined && scopeFilter!(eid); }) : ops; const activity: WakeupActivity[] = filteredOps.slice(0, maxActivity).map(op => { const a: WakeupActivity = { ts: op.ts, tool: op.tool, actor: op.actor?.name ?? 'unknown', }; const eid = opEntityId(op); if (eid) a.entity_id = eid; return a; }); return { ...(identity ? { identity } : {}), ...(params.scope ? { scope: params.scope } : {}), now: { active_tasks: activeTasks, current_epics: currentEpics, }, recent: { completions, activity, }, metadata: { generated_at: new Date().toISOString(), identity_present: identity !== undefined, active_task_count: activeTasks.length, epic_count: currentEpics.length, completion_count: completions.length, activity_count: activity.length, }, }; } - MCP tool handler registration for backlog_wakeup. Defines the input schema (scope, max_completions, max_activity, evidence_snippet_chars), injects filesystem identity reader and operation logger, calls the core wakeup function, and formats the response.
export function registerBacklogWakeupTool( server: McpServer, service: IBacklogService, deps?: BacklogWakeupDeps, ): void { server.registerTool( 'backlog_wakeup', { description: 'Dense session-start briefing: active tasks, current epics, recent completions (with evidence snippets), and recent activity. No focal entity required — use this at the start of every session to understand what you were working on. Optional `scope` narrows to a folder (for project-scoped briefing), milestone, or epic.', inputSchema: z.object({ scope: z.string().optional().describe( 'Optional entity ID to scope the briefing to a subtree. Must be a container (folder/milestone/epic). Use a folder ID for project-scoped wake-up (e.g. "FLDR-0001"). Omit to get everything across the whole backlog.', ), max_completions: z.number().min(0).max(50).optional().describe( 'Max done tasks in the "recent" section. Default: 5.', ), max_activity: z.number().min(0).max(50).optional().describe( 'Max recent activity-log entries. Default: 5.', ), evidence_snippet_chars: z.number().min(40).max(1000).optional().describe( 'Max chars of evidence to include per completion. Default: 160.', ), }), }, async ({ scope, max_completions, max_activity, evidence_snippet_chars }) => { try { const result = await wakeup(service, { ...(scope !== undefined ? { scope } : {}), ...(max_completions !== undefined ? { maxCompletions: max_completions } : {}), ...(max_activity !== undefined ? { maxActivity: max_activity } : {}), ...(evidence_snippet_chars !== undefined ? { evidenceSnippetChars: evidence_snippet_chars } : {}), readIdentity: readIdentityFile, ...(deps?.operationLogger ? { readOperations: (o) => deps.operationLogger!.read(o) } : {}), }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] }; } catch (e) { if (e instanceof ValidationError) { return { content: [{ type: 'text', text: JSON.stringify({ error: e.message }) }], isError: true, }; } throw e; } }, ); - WakeupParams interface — all optional parameters for the wakeup core function: scope, maxCompletions, maxActivity, evidenceSnippetChars, readIdentity, readOperations.
export interface WakeupParams { /** * Restrict the briefing to a single subtree — a Folder, Milestone, or Epic * entity ID. Every active task, epic, completion, and activity entry is * filtered to descendants (transitively) of this entity. The scope entity * itself is not included in the result sections. * * **Typing decision:** ``scope`` is declared as ``string`` to match the * whole codebase's ID-field convention (``parent_id``, ``epic_id``, * all MCP tool args — every ID is a plain string, validated at boundaries * via ``isValidEntityId`` / ``parseEntityId`` from ``@backlog-mcp/shared``). * Introducing a branded ``EntityId`` type for one field would fight the * rest of the system; it would also be a weaker guarantee than what * we actually do — at the core boundary we validate two things that a * brand can't express: * 1. The ID is well-formed (parses via ``parseEntityId``) * 2. The referenced type is a **container** (folder/milestone/epic), * not a leaf (task/artifact/cron) * Both checks happen at the start of ``wakeup``; invalid scopes throw * ``ValidationError`` with a message that names the offending ID. * * **What counts as a "container":** any substrate whose definition has * ``structure.isContainer === true`` — currently ``folder``, ``milestone``, * ``epic``. Leaf substrates (``task``, ``artifact``, ``cron``) are rejected. * * **Recommended usage:** Folders are the intended "project" abstraction. * A top-level Folder (``parent_id === undefined``) acts as a project; * nested folders act as sub-areas. Use ``scope: "FLDR-0001"`` for * project-scoped wake-up, ``scope: "EPIC-0005"`` to narrow to an epic. * Omit ``scope`` to get everything across all projects. */ scope?: string; /** Max recent completions in the "recent" section. Default: 5. */ maxCompletions?: number; /** Max recent activity entries (from the operation log). Default: 5. */ maxActivity?: number; /** Evidence snippet max chars on completion summaries. Default: 160. */ evidenceSnippetChars?: number; /** * Synchronous identity loader. Return ``undefined`` for "no identity * configured" — core will omit the L0 section. Omit to skip entirely. */ readIdentity?: () => string | undefined; /** * Recent operation reader — returns write-log entries newest-first. * Injected because the operation log is outside ``IBacklogService`` and * core must not import the concrete logger (keeps core transport-free). * Omit to skip the activity section entirely. */ readOperations?: (options: { limit?: number }) => Array<{ ts: string; tool: string; params: Record<string, unknown>; resourceId?: string; actor: { type: string; name: string }; }>; } - WakeupEntitySummary, WakeupCompletion, WakeupActivity, and WakeupResult interfaces — the return types for the wakeup briefing.
export interface WakeupEntitySummary { id: string; title: string; status: Status | string; type: string; parent_id?: string; updated_at?: string; } export interface WakeupCompletion extends WakeupEntitySummary { evidence_snippet?: string; } export interface WakeupActivity { ts: string; tool: string; entity_id?: string; actor: string; } export interface WakeupResult { identity?: string; /** Echoes the scope param so callers can confirm what was included. */ scope?: string; now: { active_tasks: WakeupEntitySummary[]; current_epics: WakeupEntitySummary[]; }; recent: { completions: WakeupCompletion[]; activity: WakeupActivity[]; }; metadata: { generated_at: string; identity_present: boolean; active_task_count: number; epic_count: number; completion_count: number; activity_count: number; }; } - packages/server/src/tools/index.ts:53-55 (registration)Tool registration call in the central registerTools function — wires backlog_wakeup with the operationLogger dependency.
registerBacklogWakeupTool(server, service, deps?.operationLogger ? { operationLogger: deps.operationLogger } : undefined); registerBacklogRecallTool(server, deps?.memoryComposer ? { memoryComposer: deps.memoryComposer } : undefined); }