Standup Pulse is a local-first async standup agent for Slack with an Angular admin dashboard. It runs an optimized Unsloth Gemma 4 26B A4B MoE GGUF with MTP speculative decoding through llama.cpp, uses Mastra for the agent, CopilotKit Channels for Slack transport, and CopilotKit Angular with model-generated A2UI in the dashboard.
- Nx monorepo, pnpm, Node.js
- Angular 22, NgRx SignalStore 22 RC, Tailwind CSS 4, daisyUI 5
- Hono, SQLite, Drizzle ORM
- Mastra, AG-UI, CopilotKit Runtime and Channels
unsloth/gemma-4-26B-A4B-it-GGUF:UD-Q4_K_Mwith its MTP drafter on llama.cpp
Install dependencies:
pnpm installStart the local model in one terminal:
tools/model/start-local-model.sh q4Start the dashboard and its API/Channel dependency in another terminal:
pnpm nx serve dashboardThe dashboard is available at http://localhost:4200. The API listens on http://127.0.0.1:3000; /api/status reports the model, agent, database, scheduler, and managed Channel states.
With the model running, start Studio in a third terminal:
pnpm nx run api:studioOpen http://localhost:4111, select Standup Pulse, then choose the
Ada · local admin request-context preset. Studio can run the same local
agent against the same standup database and shows its model calls, tool calls,
inputs, outputs, timings, and trace tree. This local Studio is intentionally a
development surface; do not expose port 4111 publicly without adding Studio
authentication.
Studio and the Hono API write to the same local LibSQL observability domain, so dashboard, Slack, and Studio-originated runs appear in the same trace view. Standup text and blocker fields are redacted before persistence. The Editor is a development authoring surface: changes are not hot-applied to the running API; promote reviewed changes through the shared agent source and restart the stack.
The selected CopilotKit project environment lives in ignored .env. Provider setup credentials are removable after a successful Channel reconcile and must not remain in source control.
The tracked declaration is .copilotkit/channels.json:
- Channel:
standup-pulse - Display name:
Standup Pulse - Provider: Slack
- Test channel:
#standup-bot-testing
Check the managed state with:
npx --yes copilotkit@4.8.1 channels status --jsonThe runtime subscribes a thread on @standup-pulse mention, answers later
unmentioned messages only inside that subscribed thread, and remains silent in
unrelated conversations. CopilotKit Channels routes a mentioned event to the
mention handler instead of also invoking the ordinary message handler, so one
inbound event produces one agent run.
The Channel boundary is deliberately split by responsibility:
apps/api/src/channels/standup-channel-domain.tsdefines the small application port required by model-controlled Channel tools.apps/api/src/channels/channel-handlers.tsowns mention, subscription, prompt, and visible failure behavior.apps/api/src/channels/standup-channels.tsonly validates and composes the managed Channel.apps/api/src/channels/standup-tools.tsxdefines the three domain-backed tools: submit a standup, render the team pulse, and render the blocker digest.apps/api/src/channels/standup-cards.tsxcontains portable Channel cards;team-pulse-chart.tsxcontains the Slack-only chart.apps/api/src/runtime/channel-runtime.tsowns Intelligence configuration and Channel lifecycle.standup-channel-domain-adapter.tsconnects the Channel port to the standup service and trusted Slack identity resolver.
Identity, source-message references, and the current work date come from trusted runtime context rather than model arguments. Standup writes accept only a human Slack actor. Read-only cards remain provider-portable, with the native trend chart added only on Slack.
pnpm exec prettier --check .
pnpm nx run-many -t lint,test,build --all --skip-nx-cache
pnpm exec tsc -p apps/api/tsconfig.app.json --noEmit
RUN_LOCAL_MODEL_TESTS=1 pnpm nx test api --skip-nx-cache
pnpm nx e2e dashboard-e2e --skip-nx-cacheThe pinned-artifact and smoke-test procedure is documented in docs/model/local-gemma-runbook.md.
apps/dashboard— Angular CopilotKit dashboard, organized as a DDD structure inside the app:src/app/core— app-wide configuration (CopilotKit setup)src/app/domains/pulse/feature-*— routed feature slices (shell, today, team, history, settings)src/app/domains/pulse/ui— presentational components (no store access)src/app/domains/pulse/data—PulseApiClient+ SignalStores composed from customsignalStoreFeaturessrc/app/domains/pulse/util— pure helpers
apps/api— Hono API, Copilot runtime, and Channel lifecycle host, organized as a DDD structure inside the app:src/domain— standup business rules (models, ports, service)src/data— SQLite/Drizzle persistence and schedulingsrc/agent— Mastra agent and local-model adaptersrc/channels— Channel contracts, inbound handlers, tool definitions, portable cards, and Slack-specific presentationsrc/http— Hono routes and error handlingsrc/runtime— Copilot runtime and Channel integrationsrc/slack— proactive nudge deliverysrc/config— environment and channel configurationsrc/studio— local Mastra Studio entry point (pnpm nx studio api)
libs/shared/contracts— shared Zod contracts and protocol identifierstools/model— llama.cpp startup, smoke-test, and model-manifest tooling