Final milestone of docs/plans/llm-fallback-aliases.md. Every backend
caller now requests models via the `mana/<class>` alias system instead
of hardcoded `ollama/...` strings. mana-llm resolves aliases through
`services/mana-llm/aliases.yaml` with health-aware fallback (M3) and
emits resolved-model + fallback metrics (M4).
SSOT moved to `packages/shared-ai/src/llm-aliases.ts` so apps/api,
apps/mana/apps/web, and services/mana-ai all import the same
`MANA_LLM` constant via the existing `@mana/shared-ai` workspace
dependency. Three additional sites (memoro-server, mana-events,
mana-research) inline the alias string with a SSOT comment because
they don't pull @mana/shared-ai today.
Migrated 14 sites across 10 files:
- apps/api: writing(LONG_FORM), comic(STRUCTURED), context(FAST_TEXT),
food(VISION), plants(VISION), research orchestrator (3 tiers
collapsed to STRUCTURED+FAST_TEXT/LONG_FORM)
- apps/mana/apps/web: voice/parse-task + parse-habit (STRUCTURED)
- services/mana-ai: planner llm-client + tick.ts (REASONING)
- services/mana-events: website-extractor (STRUCTURED, inlined)
- services/mana-research: mana-llm client (FAST_TEXT, inlined)
- apps/memoro/apps/server: ai.ts (FAST_TEXT, inlined)
Legacy env-vars removed: WRITING_MODEL, COMIC_STORYBOARD_MODEL,
VISION_MODEL, MANA_LLM_DEFAULT_MODEL. The chain in aliases.yaml is
now the single tuning surface; SIGHUP reloads it without redeploys.
New `scripts/validate-llm-strings.mjs` regex-scans 2538 files for
hardcoded `<provider>/<model>` strings and fails the build if any
land outside the SSOT or the explicitly-allowed paths (image-gen
modules, model-inspector code, this validator itself, the registry).
Wired into `validate:all` next to the i18n + theme validators.
Verified: `pnpm validate:llm-strings` clean, `pnpm --filter @mana/api
type-check` clean, `pnpm --filter @mana/ai-service type-check`
clean. Web type-check has 2 pre-existing errors in
SettingsSidebar.svelte (i18n MessageFormatter type drift, last
touched in 988c17a67 — unrelated to this work).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Persona-Runner / Claude Desktop / Web-App-Mission-Runner können jetzt
Comic-Characters bauen, iterieren und pinnen — same Auto/Propose-
Pattern wie die Story-Tools.
MCP (packages/mana-tool-registry/src/modules/comic.ts):
- comic.listCharacters (read/auto): Pull, decrypt, filter (style?,
favoriteOnly?), liefert {id, name, style, addPrompt, source-Refs,
variantMediaIds, pinnedVariantId, variantCount, tags, isFavorite}.
- comic.createCharacter (write/propose): legt nur die Row an —
trennt Anlegen von Generierung damit der Agent reviewen kann
bevor Credits fließen. Liefert characterId zurück.
- comic.generateVariant (write/propose, kostet Credits): pullt
Character-Row, dekodiert, ruft /picture/generate-with-reference
mit n=count (default 4) + Stil-Prefix + Identity-Anchor-Prompt,
schreibt N picture.images mit comicCharacterId-Back-Ref, pusht
field-level Update auf variantMediaIds + pinnedVariantId
(auto-pin auf erste neue Variant wenn vorher null).
- comic.pinVariant (write/propose): Set-Equality-Check (variantMediaId
muss in variantMediaIds sein), field-level Update auf
pinnedVariantId. Snapshot-Pattern: bestehende Stories bleiben
unverändert, nur neue Stories nutzen den neuen Pin.
AI_TOOL_CATALOG (packages/shared-ai/src/tools/schemas.ts):
- list_comic_characters (auto)
- create_comic_character (propose) — auto-resolvt face/body-refs aus
meImages-primaries, Agent muss keine mediaIds kennen
- generate_character_variant (propose, count 1-4)
- pin_character_variant (propose)
Web-App-Executors (apps/mana/apps/web/src/lib/modules/comic/tools.ts):
- 4 ModuleTool-Einträge, die an comicCharactersStore +
runCharacterGenerate delegieren — gleicher Code-Pfad wie die UI,
also keine Divergenz zwischen Klick und Agent-Call.
Comic-Autor-Template (packages/shared-ai/src/agents/templates/
comic-author.ts):
- Policy bi-lingual erweitert: snake_case + dot-case Namen für
alle 4 neuen Character-Tools.
- System-Prompt Schritt 3 ergänzt: "Wenn der User noch keinen
passenden Comic-Character hat → list_comic_characters →
create_comic_character → generate_character_variant → pin.
Das ist EINMALIG — der gepinnte Character bleibt für viele
Stories der stabile Identity-Anchor."
- Tool-Liste am Ende vom System-Prompt um den Character-Pfad
ergänzt.
apps/mana/CLAUDE.md Tool-Coverage-Zeile für comic erweitert:
+ create_comic_character / generate_character_variant /
+ pin_character_variant (propose)
+ list_comic_characters (auto)
Tool-Count: comic 3→7. Module 23 unverändert.
107 shared-ai-Tests weiter grün. check für comic-Files clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the Augur module: capture omens, fortunes, and hunches in
a poetic Witness mode and read them back empirically in Oracle mode.
Same data, two lenses; the killer mechanic is the Living Oracle that
materialises empirical reflections from the user's own resolved
history at capture time.
Why now: docs/future/MODULE_IDEAS.md captured the brainstorm, then
the spec landed at docs/plans/augur-module.md as a Witness+Oracle
hybrid. Built end-to-end through M6 in one go.
Highlights:
- Witness gallery + DueBanner + DetailView + Resolve flow
- Oracle stats: calibration-per-source, vibe-hit-rate, cross-module
correlation engine (mood/sleep/duration after-windows)
- Living Oracle: deterministic fingerprint+match against user's own
resolved history; cold-start-gated at 50 resolved entries
- Year-Recap view at /augur/recap/[year]
- 5 MCP tools: capture_sign, resolve_sign, list_open_signs,
consult_oracle, augur_year_recap (in AI_TOOL_CATALOG)
- Visibility integration: default 'private', VisibilityPicker in
DetailView. Server-side unlisted-snapshot-publish stays follow-up
- v47 Dexie schema; encrypted: source/claim/feltMeaning/
expectedOutcome/outcomeNote/tags/livingOracleSnapshot
- LOCAL TIER PATCH: requiredTier 'guest' for testing
Strings interpolated through `T` constants so the i18n-hardcoded
baseline stays at 0 for augur — real $_('augur.*') keys land later.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comic nutzte bisher 'openai/gpt-image-2' hartcodiert auf drei Ebenen
(generate-panel.ts, comic.generatePanel MCP-Tool, generate_comic_panel
AI-Tool). Wardrobe hat seit dem Nano-Banana-Commit einen
TryOnModelPicker mit drei Optionen — Comic spiegelt das jetzt 1:1.
Wählbar in allen drei Editoren (PanelEditor, BatchPanelEditor,
StoryboardSuggester):
- openai/gpt-image-2 (Default) — OpenAI GPT-image Standard
- google/gemini-3-pro-image-preview — Nano Banana Pro, hohe
Konsistenz, teurer
- google/gemini-3.1-flash-image-preview — Nano Banana 2, neuestes,
schnell, günstig
Implementierung:
- api/generate-panel.ts: PanelModel Union + DEFAULT_PANEL_MODEL +
model? Param auf RunPanelGenerateParams + im HTTP-Body
weitergereicht (vorher hart 'openai/gpt-image-2').
- components/PanelModelPicker.svelte: neue Komponente, Stil/Markup
identisch zu TryOnModelPicker für Muskel-Memory über beide Flows.
- components/PanelEditor.svelte: `let model = $state(DEFAULT_PANEL_MODEL)`
+ Picker oberhalb der Qualität-/Format-Leiste + model im
runPanelGenerate-Call.
- components/BatchPanelEditor.svelte: gleiche Änderung — ein Model
pro Batch (nicht pro Row) damit der Batch konsistent rendert.
- components/StoryboardSuggester.svelte: gleiches Pattern; der
Picker landet zwischen "Panel manuell"-Button und dem
Qualität/Format-Block.
- packages/mana-tool-registry/src/modules/comic.ts: generatePanel
Input-Schema bekommt model mit zod.enum() + default; im Body
wird input.model durchgereicht.
- packages/shared-ai/src/tools/schemas.ts: generate_comic_panel
bekommt Parameter 'model' optional mit gleicher Enum-Liste.
- apps/mana/apps/web/src/lib/modules/comic/tools.ts: isValidModel
Guard + Parameter-Validierung; model an runPanelGenerate.
Keine Story-Level-Persistierung — model bleibt lokaler State pro
Editor-Mount. Eine model-Spalte auf comicStories würde Migration
brauchen und die Wahl ist eh ad-hoc pro Panel/Batch.
Plan-Doc (§2.1) dokumentiert die Entscheidung + die drei Optionen.
107 shared-ai tests weiter grün. check + validate:all clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Macht den Comic-Autor-Template (M6) auch im Web-App-Mission-Runner
nutzbar. Bisher war der Template nur über persona-runner/Claude
Desktop sinnvoll, weil die comic.*-Tools nur im mana-tool-registry
(MCP) lagen. Jetzt kennt die AI Workbench drei neue Tools und der
Template-Policy-Map trägt beide Naming-Konventionen.
AI_TOOL_CATALOG-Einträge (packages/shared-ai/src/tools/schemas.ts):
- list_comic_stories (auto) — filter style?/favoriteOnly?/limit?
- create_comic_story (propose) — title + style + optional
description/storyContext/tags. Character-Refs werden vom Executor
automatisch aus meImages primary face-ref + body-ref gezogen,
also muss der Planner keine mediaIds kennen.
- generate_comic_panel (propose) — storyId + panelPrompt + optional
caption/dialogue + quality. Kostet Credits.
Executors (apps/mana/apps/web/src/lib/modules/comic/tools.ts):
- list: scopedForModule pull + decrypt + filter + sort newest.
- create: resolveCharacterMediaIds() scannt meImagesTable für das
aktive Space, nimmt face-ref+body-ref. Fehler wenn kein Face
hinterlegt ("Lade eines in /profile/me-images hoch"). Delegiert
an comicStoriesStore.createStory — gleiche encryption/event-
pipeline wie StoryForm.
- generate: lädt Story decrypted, delegiert an runPanelGenerate
(identischer Pfad wie PanelEditor in der UI), liefert
panelIndex + imageUrl zurück.
Registrierung in data/tools/init.ts (registerTools(comicTools)).
Template-Policy (comic-author.ts) jetzt bi-lingual: snake_case
(AI_TOOL_CATALOG) UND dot-case (MCP) nebeneinander in tools-Map.
So gilt die Intent-Policy konsistent egal welche Runner-Oberfläche
das Tool nennt — auto für list_comic_stories / comic.listStories,
propose für create_comic_story / comic.createStory /
generate_comic_panel / comic.generatePanel / comic.reorderPanels.
apps/mana/CLAUDE.md Tool-Coverage-Tabelle bekommt eine Comic-Zeile.
Tool-Count jetzt 75→78, Module 22→23. 107 shared-ai tests
weiter grün. check + validate:all clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Agents can now pin a default writing style. When an AI-actor runs
`create_draft` without an explicit styleId, the tool resolves to the
agent's `defaultWritingStyleId` so e.g. a "Marketing-Agent" always
drafts in the Corporate-Tone style and a "Memoir-Agent" in Memoir.
- @mana/shared-ai: optional `defaultWritingStyleId?: string` added to
the Agent interface (plaintext FK, format `preset:<id>` or a custom
WritingStyle uuid). No migration — existing rows stay undefined and
the fallback path no-ops for them.
- ai-agents store: field threaded through CreateAgentInput + AgentPatch
+ the create function's copy-list. `updateAgent` already deep-clones
the patch so nothing else to change there.
- ai-agents ListView: new "Writing" section in the agent detail panel
with a StylePicker (reuses the writing module's component — Vorlagen
+ Meine Stile optgroups). Empty = kein Default.
- writing/tools.ts: `resolveAgentDefaultStyle()` reads the current
actor, guards `isAiActor`, loads the agent row, and returns its
defaultWritingStyleId. Wired into `create_draft` as a fallback when
`params.styleId` is missing. User-invoked calls skip the lookup — a
human omitting styleId means "ad-hoc, no style", not "my default".
`generate_draft_content` needs no change because the draft's styleId
is already set at create time.
107 shared-ai tests still pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neuer Eintrag in der Template-Galerie unter /agents/templates:
Comic-Autor nimmt einen Tagebuch-Eintrag, eine Notiz oder ein
Library-Review und verwandelt ihn in eine kurze Panel-Folge —
4 Panels Default, Sprechblasen + Captions direkt im Bild durch
gpt-image-2.
Policy-Layout:
- comic.listStories / journal.* / notes.* / library.* / kontext /
goals → auto. Der Agent darf frei stöbern, ohne den User für
jeden Read anzunerven.
- comic.createStory / comic.generatePanel / comic.reorderPanels →
propose. Jedes Write muss der User bestätigen; besonders
generatePanel, das pro Call 3-25 Credits kostet.
- Baseline: alle propose-fähigen Tools aus AI_TOOL_CATALOG kriegen
propose (seed wie im Recherche-Agent) — Cross-Module-Schreibungen
die der Agent eventuell vornimmt (z.B. create_note für eine
Sidecar-Zusammenfassung) landen so als Vorschlag, nicht als
Blitz-Ausführung.
- defaultForAi: propose — sicher per Default.
System-Prompt gibt dem Agent eine klare Rolle: Text lesen, Stil
wählen nach Ton (comic/manga/cartoon/graphic-novel/webtoon), 4
Panels mit prompt+caption?+dialogue? vorschlagen, Protagonist ist
immer der User. "Humor wenn der User es leicht nimmt, ernst wenn
er es ernst nimmt. Nie urteilen." Ton-Hinweis zu englischen vs.
deutschen Dialog-Texten (Englisch rendert stabiler).
Szene öffnet Comic + Journal + ai-missions + ai-workbench nebeneinander.
Eine paused Starter-Mission "Comic aus einem Tagebuch-Eintrag" mit
Concept-Markdown-Vorlage (Eintrag / Stil / Panel-Anzahl / Ton).
Die comic.*-Tools leben in mana-tool-registry (MCP) und sind noch
NICHT im AI_TOOL_CATALOG — dieser Template ist primär für
persona-runner/Claude-Desktop-Seite nutzbar, bis die Workbench-
Integration separat folgt.
107 shared-ai tests weiter grün.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Writing is now programmatically accessible from the foreground mission
runner, personas, and Claude Desktop / MCP. Eight tools land:
Auto (read-only):
- list_drafts — filtered by kind/status + word-count summary
- get_draft — briefing + current version body, ready for reading
- list_writing_styles — 9 presets + user customs, ids usable in create_draft
Propose (human approval per agent policy):
- create_draft — briefing only, no generation yet
- generate_draft_content — wraps generationsStore.startDraftGeneration;
writes a new LocalDraftVersion + pointer flip
- refine_draft_selection — wraps refineSelection + applyRefinement in
one call; operations: shorten/expand/tone/
rewrite/translate with op-specific params
- set_draft_status — draft/refining/complete/published
- save_draft_as_article — hand-off to articlesStore.saveFromExtracted
with internal://writing/<id> as originalUrl,
records publishedTo + emits WritingDraftPublished
Schemas live in @mana/shared-ai/src/tools/schemas.ts (the SSOT that the
web-app policy layer + mana-ai planner derive from). Executors live in
modules/writing/tools.ts and delegate to the existing stores so the
encryption + event pipeline runs once regardless of who called the tool.
Registration added to data/tools/init.ts.
107 shared-ai tests still pass. CLAUDE.md tool-coverage table bumped:
67→75 tools, 21→22 modules.
Not in M8 (deferred): agent.defaultWritingStyleId linkage (needs a
Persona schema change + runner wiring), mana-tool-registry Zod specs
(add when a non-web MCP client needs richer validation).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exposes runSubAgent() as a tool the planner LLM can call natively,
matching Claude Code's `Task` tool shape: { subagent_type, description,
prompt } -> single-string summary.
New exports from @mana/shared-ai:
- TASK_TOOL_NAME = 'task'
- TASK_TOOL_SCHEMA — ToolSchema ready to drop into a runPlannerLoop
`tools` array. subagent_type enum = research|plan|general;
description+prompt required; defaultPolicy: 'auto' (control-flow,
not a user-data write).
- createTaskToolHandler(opts) — factory returning:
- handle(call): structured ToolResult with the sub-agent's
summary as message + data {subAgentType, toolsCalled,
rounds, stopReason, usage}
- cumulativeUsage(): rolled-up TokenUsage across all sub-agent
invocations — parent budget accounting reads from here
- invocationCount(): metric-ready counter
Why not in mana-tool-registry: `task` is a loop-internal control-flow
primitive, not a user-data operation. Registry is for habits/notes/etc.
where MCP exposure and space-scoping matter. task never touches mana-
sync and never crosses the MCP boundary.
Recursion guard is defense-in-depth: the primitive throws
SubAgentRecursionError, this handler catches parentDepth >=
MAX_SUB_AGENT_DEPTH up front and returns a structured ToolResult
instead so the LLM sees it as regular tool-feedback.
Exceptions from the sub-agent (provider down, network) get wrapped
as `{ success: false, message: 'Sub-agent failed: ...' }`. The parent
loop's round continues.
14 new tests covering schema shape, recursion rejection, argument
validation (4 cases), happy path with tool dispatch, cumulative
usage tracking across multiple invocations, exception wrapping,
and parent-dispatcher routing.
107 shared-ai tests green total (was 93).
M3.3 consumer wiring follows.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New packages/shared-ai/src/planner/sub-agent.ts implementing the
"one level deep, fresh messages, restricted tools, single-string
return" sub-agent contract from Claude Code's KN5/I2A launcher.
Four invariants enforced at the primitive level:
1. FRESH messages[] — parent's history never leaks in. The sub-agent
only sees its own system prompt + the task description. Hundreds
of scanned files stay inside the sub-agent.
2. RESTRICTED tool-whitelist — parent's full catalog is filtered
per SubAgentType ('research' = auto-policy only, 'general' =
everything, 'plan' = auto-policy + 3-round cap). Custom filter
overrides the type default.
3. SINGLE RETURN VALUE — sub-agent returns summary:string for
the parent to render as task-tool-result. Individual tool calls
stay in rawResult for debug capture but never cross the boundary.
4. ONE LEVEL DEEP — MAX_SUB_AGENT_DEPTH = 1. parentDepth >= 1 throws
SubAgentRecursionError; the consumer task-tool handler will
also check, this is defense-in-depth.
Model is required (no default) — routing to a cheaper tier like the
compactor does is an explicit decision, not a sneaky default.
Belt-and-suspenders wrapper on onToolCall rejects any tool call
whose name isn't in the whitelist, even if the LLM fabricates one.
14 new tests covering recursion guard, tool filtering per type,
custom filter, whitelist rejection, fresh-messages isolation, usage
roll-up, default summary on max-rounds, type-specific system prompt,
system-prompt override, and end-to-end tool-call -> result -> summary.
93 shared-ai tests green total (was 79).
M3.2 (task tool in registry) and M3.3 (consumer wiring) follow.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
compactHistory() now defaults to DEFAULT_COMPACT_MODEL =
'google/gemini-2.5-flash-lite' when the caller doesn't override. Lite
is ~3–5x cheaper than gemini-2.5-flash with near-identical
summarisation quality — summarisation doesn't need the same tier as
reasoning + tool-calling, and the compactor fires exactly when token
spend is highest, so the cheaper route saves exactly where it matters.
CompactHistoryOptions.model is now optional. All three consumers
(mana-ai tick, webapp Companion, webapp Mission runner) drop their
explicit gemini-2.5-flash override and let the default apply.
This is the pragmatic M2.5: no mana-llm changes. The "tier" abstraction
(X-Model-Tier header, env-routed aliases) from the Claude-Code report
makes sense only once multiple utility tasks need cheaper routing —
topic-detection, classification, command-injection checks. Today only
the compactor wants it, and a model constant is the simplest contract
that works.
2 new tests (default applied + override honoured). 79 shared-ai tests
green, all three consumers type-check clean. One pre-existing unrelated
type error in apps/mana/apps/web/src/lib/modules/wardrobe/queries.ts
(not touched by this commit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the loop on M2: when the compactor fires, the LLM needs to know
it's now seeing a <compact-summary> instead of raw turns so it
doesn't waste a turn asking about lost details or re-executing tools
whose responses are gone.
shared-ai:
- LoopState grows `compactionsDone: number` (cap-1 by current loop
policy, but shape kept as count for future multi-compact cycles).
- runPlannerLoop populates it on each reminder-channel call. New
loop test asserts [0, 1] sequence: round 1 before compaction,
round 2 after.
mana-ai:
- New producer `compactedReminder` — fires severity=info when
compactionsDone >= 1, wrapped in a German one-liner ("frag nicht
nach verlorenen Details").
- Injected FIRST in buildReminderChannel so the LLM frames the rest
of the round with "I'm looking at a summary" context. Metric
surface stays `{producer='compacted', severity='info'}`.
4 new reminder tests (3 pure producer + 1 composition-ordering) +
1 loop-wiring test. 77 shared-ai, 20 reminders.test.ts — green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PlannerLoopInput grows an optional compactor:
compactor?: {
maxContextTokens: number;
threshold?: number; // default 0.92, matches Claude Code wU2
compact: (messages) => Promise<{ messages, compactedTurns }>;
}
Before each LLM call the loop checks whether promptTokens+completion
has crossed threshold × maxContextTokens. If yes AND we haven't
compacted this run yet, the callback runs, its returned messages
REPLACE the live history, and compactionsDone flips to 1 so a
runaway tool can't re-trigger.
Design choices:
- Fires at most ONCE per loop run. If the fresh (compacted)
history hits the threshold again in the same run, the LLM
round budget will hit first; better to terminate than to
recursively compact a summary.
- No reminder emitted automatically — the caller can wire
that via reminderChannel by reading compactionsDone from
LoopState (next PR; compactionsDone isn't exposed yet to
keep the state surface small).
- compactor callback is injectable, not hardcoded to
compactHistory() from compact.ts. Lets mana-ai route the
compactor LLM call to a cheaper model (Haiku) without
changing the loop.
- Zero maxContextTokens → skip silently (same contract as
shouldCompact()).
Also cleaned up the isParallelSafe non-null-assertion warning by
hoisting the predicate to a local with proper narrowing.
5 new loop tests: below-threshold no-op, single-fire replacement,
once-per-run idempotency, zero-cap bail, no-op when compactor
returns 0 turns. 76 shared-ai tests total, green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Claude-Code wU2 pattern: when token usage hits ~92% of the provider's
context budget, fold all pre-tail turns into a single structured summary
(Goal / Decisions / Tools Called / Current Progress) so subsequent
rounds see a synopsis instead of the raw log.
This commit ships ONLY the primitive. Wiring it into runPlannerLoop
(auto-trigger before the next LLM call when shouldCompact() fires)
is M2.2 so the surface stays small and testable.
New exports from @mana/shared-ai:
- shouldCompact(totalTokens, maxContextTokens, threshold?)
→ boolean; DEFAULT_COMPACT_THRESHOLD = 0.92, matching Claude Code.
Bails safely when maxContextTokens is missing (local models often
don't report usage).
- compactHistory(messages, { llm, model, keepRecent?, temperature? })
→ { messages, summary, compactedTurns, usage? }
Preserves: [0]=system, [1]=first user, [last N]=recent turns
(default 4). Everything between gets sent through the compact
agent with COMPACT_SYSTEM_PROMPT — a fixed 4-section Markdown
schema. Temperature default 0.2 because we want summarisation,
not creativity.
- parseCompactSummary / renderCompactSummary — round-trip helpers.
Parser is tolerant (missing sections → empty string) so a partial
compaction still produces a usable summary.
The summary replaces the middle as a single role='assistant' message
wrapped in <compact-summary> tags. Assistant role (not system) because
some providers reject arbitrary system messages deep in history.
Tests: 17 new across the 4 exports (trigger logic, Markdown round-trip,
structural preservation of anchors + tail, usage passthrough, custom
keepRecent). All 71 shared-ai tests green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two things:
1. AI tools (9) in the website module — writes go through the standard
proposal flow, reads run auto during planning.
- shared-ai/src/tools/schemas.ts: AI_TOOL_CATALOG entries with
defaultPolicy propose/auto.
- webapp modules/website/tools.ts: execute functions wired to the
existing stores. ModuleTool[] registered in data/tools/init.ts.
- Propose: create_website, apply_website_template, create_website_page,
add_website_block, update_website_block, publish_website
- Auto: list_websites, list_website_pages, list_website_blocks
Server-side mana-tool-registry integration (mana-mcp, mana-ai) is
a M5.x follow-up — webapp flow unblocks the missions-based use case.
2. Starter templates — clone into a fresh site with new UUIDs.
- templates/types.ts: SiteTemplate shape with localId / parentLocalId
so container→child references survive the clone.
- 4 templates: portfolio (4 pages), personal-linktree (1 page, 6 CTAs),
event (3 pages incl. RSVP form), blank (1 empty page). Deferred:
smb-corporate + product-landing (need team/pricing/testimonials
blocks, M6+).
- sitesStore.applyTemplate: walks template, bulk-inserts new rows,
remaps parent refs. Sets navConfig items from template pages.
- TemplatePicker component + /website/new route. Replaces the old
quick-create modal; ListView now links to /new. AppRegistry
context-menu action points there too.
AiProposalInbox integration deferred — the component doesn't exist in
the webapp yet (the plan mentions it aspirationally). defaultPolicy
'propose' is already set so writes stage correctly once the UI catches
up.
Validation:
- pnpm run validate:all: 6/6 gates green
- pnpm run check (web): 0 errors, 0 warnings
- apps/api + packages/shared-ai type-check: green
Plan: docs/plans/website-builder.md (M5 shipped)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends LoopState with a sliding window of the last N ExecutedCalls
(oldest-first), capped at LOOP_STATE_RECENT_CALLS_WINDOW = 5. The loop
maintains the window automatically; reminderChannel producers read it
without touching internal state.
This activates retryLoopReminder which was shape-only in faa472be9.
The guard now fires end-to-end: when round >= 3 and the tail-2 calls
both returned success:false, the LLM sees a "stop retrying, write a
summary instead" <reminder> on the next turn. The tail-2 check rather
than window-wide is deliberate — a flaky run with intermittent success
(F, F, F, OK, F) is not a retry loop, just flaky tools.
Why window=5: retry loops usually manifest within 2-3 consecutive
rounds; a 5-deep window gives room for burst-detection and
stale-tool heuristics without bloating the reminder channel. Cap
keeps the reminder producers O(5) regardless of loop length.
Tests: 3 new (sliding-window cap + slide + order in shared-ai, retry
composition + budget+retry chain + tail-only heuristic in mana-ai).
Total agent-loop tests now 74 across both packages.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wires the M1 reminderChannel into the mana-ai mission runner with two
initial producers in services/mana-ai/src/planner/reminders.ts:
- tokenBudgetReminder — warns at 75% of the agent's daily cap, emits a
stronger "wrap up NOW" message at/above 100%. Uses pretick usage +
accumulated round usage so the warning tracks drift during a long
plan.
- retryLoopReminder — shape is in place (round≥3 + last 2 failures),
currently limited to the single lastCall LoopState exposes. Extends
cleanly once LoopState carries the full failure window.
buildReminderChannel composes active producers; the tick hoists
pretickUsage24h so the channel has the baseline. Each round the loop
re-evaluates the producers, so usage drift across rounds surfaces on
the NEXT turn.
Also exports LoopState + ReminderChannel from @mana/shared-ai top-level
so consumers don't need to reach into /planner.
Tests: 13 new bun tests covering thresholds, pretick+round summing,
composition, and per-round re-evaluation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three Claude-Code-inspired primitives for runPlannerLoop, derived from the
reverse-engineering reports in docs/reports/:
1. **Policy gate** (@mana/tool-registry) — evaluatePolicy() gates every tool
dispatch: denies admin-scope, denies destructive tools not in the user's
opt-in list, rate-limits per tool (30/60s default), flags prompt-injection
markers in freetext without blocking. Wired into mana-mcp with a
per-user rolling invocation log and POLICY_MODE env (off|log-only|enforce,
default log-only). mana-ai uses detectInjectionMarker only — tool dispatch
there is plan-only, so rate-limit/destructive checks don't apply yet.
2. **Reminder channel** (packages/shared-ai/src/planner/loop.ts) — new
reminderChannel callback in PlannerLoopInput. Called once per round with
LoopState snapshot (round, toolCallCount, usage, lastCall); returned
strings wrap in <reminder> tags and inject as transient system messages
into THIS LLM request only. Never pushed to messages[] — the Claude-Code
<system-reminder> pattern that keeps the KV-cache prefix stable.
3. **Parallel reads** (loop.ts) — isParallelSafe predicate enables
Promise.all dispatch when every tool_call in a round is parallel-safe,
in batches of PARALLEL_TOOL_BATCH_SIZE=10. Any non-safe call downgrades
the whole round to sequential. messages[] always appends in source
order, never completion order, so the debug log stays linear.
Default-off (undefined predicate) preserves pre-M1 behaviour.
Tests: 21 new in tool-registry (policy), 9 new in shared-ai (5 parallel,
4 reminder). All 74 green, type-check clean across 4 packages.
Design/plan: docs/plans/agent-loop-improvements-m1.md
Reports: docs/reports/claude-code-architecture.md,
docs/reports/mana-agent-improvements-from-claude-code.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five new entries in AI_TOOL_CATALOG (shared-ai/src/tools/schemas.ts):
list_articles auto Read-only listing with status +
query filter. Default hides
archived; 'all' includes them.
save_article propose URL → Readability → encrypted save.
Delegates to articlesStore.saveFromUrl
which already handles scope-aware
dedupe. Duplicates surface as
success:true with duplicate:true.
archive_article propose setStatus('archived') after
scoped existence check.
tag_article propose Case-insensitive dedupe over
globalTags; tagMutations.createTag
fills in when missing. Junction
write via articleTagOps.addTag.
add_article_highlight propose Snaps to the first verbatim
occurrence of `text` in the
decrypted article.content. Fails
cleanly when the snippet isn't
found — no orphan highlights.
Policy, client executor, and server planner derive automatically from
the catalog (see root CLAUDE.md §"AI Tool Catalog") so no manual
registration in policy.ts / services/mana-ai is needed.
Skipped from the M6 plan: <AiProposalInbox module="articles" />. The
component doesn't exist in the current codebase — after the
pendingProposals-table drop in Dexie v29 the inbox surface moved to
the mission-detail cross-module view, and articles proposals show up
there automatically. Documented in docs/plans/articles-module.md.
Also updated: plan doc now marks M1–M6 as DONE with commit refs and
the next-step pointer.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the M7/M9/M10 plan items in one pass since they share patterns.
ListView (M7)
- 4 stats cards at the top: versendet YTD, Ø Öffnungsrate, Ø Klickrate,
Entwürfe. Same layout pattern as invoices for consistency.
- Status filter chips with live counts per status.
- Search across name + subject.
- Row now shows open-rate per-campaign when available.
- Settings gear in the header matches the invoices polish.
Dashboard widget (M10)
- BroadcastsWidget.svelte: 2x stats (sent YTD + avg open rate), next
scheduled link, last sent link with open-rate badge. Empty state
nudges toward creating a first campaign.
- Registered as 'broadcasts' in WIDGET_REGISTRY and the component map.
- Medium default size, no requiredBackend (reads from Dexie only;
stats are mirrored from the last DetailView poll so no server
round-trip for the widget).
AI tools (M9)
- 3 tools added to @mana/shared-ai's AI_TOOL_CATALOG:
- create_campaign_draft (propose) — generates HTML body from a
topic, lands as a draft; user picks audience + sends via UI
- list_campaigns (auto) — id/name/subject/status/recipients
- get_campaign_stats (auto) — rates as 0..1 floats
- broadcast/tools.ts: execute handlers with an HTML→CampaignContent
shim (stores both html and a minimal Tiptap JSON placeholder so
ListView renders without the editor having to remount). stripHtml
helper derives plaintext.
- Registered in data/tools/init.ts after library.
Suggest-style tools (suggest_subject_lines) deliberately omitted —
they're pure generative and don't need an executor. The LLM can
produce subject ideas without a tool call.
Verified:
- pnpm check: 0 broadcast errors (4 pre-existing errors in articles
module from parallel work, not mine)
- shared-ai test suite: 44/44 green (function-schema roundtrips the
expanded catalog cleanly)
- mana-ai drift guard: 41/41 green
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Library module had no AI tool coverage post the M1 skeleton. Adds
four tools so the agent can curate the reading/watch list alongside
other modules:
- create_library_entry (propose) — books/movies/series/comics with
creators, year, status, rating, tags, genres. Default status
"planned" covers the most common flow ("add to watchlist").
- update_library_entry_status (propose) — status transitions
planned → active → completed (also paused / dropped). Auto-
stamps startedAt/completedAt on the matching transitions so the
existing Dexie projections (streaks, progress) fire correctly.
- rate_library_entry (propose) — 1-5 stars, thin wrapper over the
store's rate() method.
- list_library_entries (auto) — id/kind/title/status/rating/year,
filterable by kind + status.
Coverage table in apps/mana/CLAUDE.md updated (+library, +invoices
row that wasn't listed). Total now 67 tools / 21 modules.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The last open item from the plan. Missions can now draft invoices from
chat context, mark customer payments, and read status for autonomous
follow-up cadences.
Tool catalog (packages/shared-ai/src/tools/schemas.ts)
- create_invoice (propose) — clientName + lines[] + currency + due
- mark_invoice_paid (propose) — by id, optional back-dated paidAt
- list_invoices (auto) — with status + limit filter
- get_invoice_stats (auto) — open/overdue/YTD per currency
Had to widen the tool-parameter type vocabulary so create_invoice can
declare lines as a typed array. Touched three places:
- ToolSchema-side: the catalog's `type` string is already free-form so
'array' / 'object' just pass through
- ModuleTool-side (apps/mana/apps/web/src/lib/data/tools/types.ts): added
'array' | 'object' to the union so TS doesn't narrow the executor's
param signatures
- function-schema translator (packages/shared-ai): mapParamType +
JsonSchemaProperty both gained the two new types; the catalog-typo
guard test now uses 'fruit' as its sentinel (array no longer unknown)
Executor (apps/mana/apps/web/src/lib/modules/invoices/tools.ts)
- coerceLines accepts either a real array or a JSON-stringified array
(planners vary), skips malformed entries, converts major→minor units
- create_invoice pulls the generated number back from Dexie so the
success message shows "Entwurf 2026-0042 …" — the user recognises it
- mark_invoice_paid normalises YYYY-MM-DD → ISO so the store's timestamp
invariant (ISO throughout) stays intact
- list_invoices derives overdue on read (consistent with useAllInvoices),
returns major-unit amounts so the LLM reasons in user-facing numbers
- get_invoice_stats returns counts + open/overdue/YTD per currency
Registration: invoicesTools added to tools/init.ts. mana-ai drift guard
is happy (41/41 green); webapp + shared-ai type-check 0 errors; full
invoice test suite 59/59 green.
Closes: docs/plans/invoices-module.md §M8. All plan milestones now DONE.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Carries per-round token counts from the mana-llm response body
(prompt_tokens + completion_tokens) back through LlmCompletionResponse
→ PlannerLoopResult. The loop sums across rounds and exposes a single
aggregate on result.usage.
Lets mana-ai's tick re-activate per-agent daily-token budget tracking
— tokensUsed was stubbed to 0 in the migration commit (6) because the
loop didn't surface usage yet. Now recordTokenUsage + agentTokenUsage24h
get real numbers again, and the mana_ai_tokens_used_total Prometheus
counter is accurate.
Additive only: consumers without usage needs ignore the new field,
and providers that don't return usage produce zeros (not undefined —
the loop still exposes the object so downstream branches stay trivial).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The runPlannerLoop test file and the webapp's mission-runner test each
had their own inline scripted LLM mock — same interface, diverged
slightly. Consolidates into packages/shared-ai/src/planner/mock-llm.ts
and re-exports from the package root so any consumer can drive the
loop deterministically.
Both existing test files now use the shared client. 5 + 3 tests pass,
44 total in shared-ai still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The companion chat had its own ad-hoc 3-round tool-calling pipeline:
build a system prompt with tool descriptions, ask the LLM to emit
```tool JSON blocks, regex-extract, execute, feed back the result as
a synthetic user message. Same fragility class as the old text-JSON
planner — and now unnecessary since mana-llm speaks native function
calling.
Migrates companion/engine.ts to the shared runPlannerLoop, same as
the mission runner (commit 5a) and the server tick (commit 6). Tools
go to the LLM as proper function-schemas; tool_calls come back
structured; the executor runs them directly under USER_ACTOR.
Extends shared-ai/planner/loop.ts with an optional priorMessages[]
input field so the chat can preserve multi-turn history between
turns (missions don't need this and leave it empty).
Deletes the old llm-tasks/companion-chat.ts LlmTask wrapper. Nothing
else imported it.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the mobile-app deletion unblocked \`@context/mobile\`, five more
pre-existing failures surfaced across shared packages and two services.
All were silent-masked by the postinstall \`|| true\` for months.
- **shared-ai**: \`planner/loop.ts\` imported \`ToolSchema\` from
\`../tools/function-schema\`, which only imports (not re-exports) the
type. Fixed to import from the source (\`../tools/schemas\`).
- **shared-logger**: \`typeof window !== 'undefined'\` blows up under
tsconfigs that don't include the DOM lib (e.g. uload-server's
\`bun-types\`-only config), because shared-logger is consumed via
source import. Replaced with a \`globalThis\`-indirected check that
compiles under any lib configuration.
- **shared-hono**: \`credits.ts\` returned \`res.json()\` directly as
\`Promise<T | null>\`. Modern \`@types/node\` / undici types return
\`unknown\` strictly — cast to \`T\` at the boundary so the generic
contract is explicit.
- **uload-server**: \`routes/analytics.ts\` + \`routes/email.ts\` still
imported \`AuthUser\` from a \`middleware/jwt-auth\` module that was
deleted during the migration to \`@mana/shared-hono\`. Replaced with
\`AuthVariables\` from shared-hono, which matches the actual context
shape set by \`authMiddleware()\`.
- **manavoxel/web**: \`guestSeed\` collection entries were wrapped in
arrow functions, but \`local-store\` expects \`T[]\` directly and
iterates \`seed.length\` — which on a function is 0. The "guest
seed" was silently dead; eager-evaluating \`generateGuestWorld()\`
once and sharing the result fixes both the type and the runtime.
Verified: \`pnpm run type-check\` from the repo root now exits 0 —
76/76 tasks successful, no failures. First fully green state since
well before the postinstall \`|| true\` was introduced.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Introduces the new planner pipeline both the webapp runner and the
mana-ai tick will swap onto in the next commits. Additive for now —
the legacy buildPlannerPrompt + parsePlannerResponse stay exported so
callers can migrate one at a time; they get removed once the last
consumer is gone.
- planner/loop.ts — runPlannerLoop orchestrates a multi-turn chat
against a caller-supplied LlmClient. Tool-calls from the LLM are
handed to an onToolCall callback and their results fed back as
tool-messages. Parallel tool-calls in one turn execute sequentially
to keep the message log linear for debugging. Stops on assistant
stop, empty tool_calls, or a hard max-rounds ceiling (default 5).
- planner/system-prompt.ts — new buildSystemPrompt. ~40-line German
system frame, no tool listing (the SDK-level tools field carries
the schemas now), no JSON format example, no "please return JSON"
plea. User frame renders mission + linked inputs + last 3
iteration summaries, same as before.
- Five test cases covering the loop: immediate stop, single tool
call with result feedback, parallel calls execute in order, tool
failures propagate as tool-messages the LLM can react to, and
maxRounds ceiling fires with the right stopReason.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Single bridge between the AI_TOOL_CATALOG shape and the wire format every
provider (Gemini, OpenAI-compat, Ollama ≥ 0.3) speaks for native tool
calling. Keeps the catalog as the source of truth — the runner never
reads catalog entries directly; it asks this converter for function-spec
shapes to hand the LLM.
- No _rationale or wrapper-tool injection: the runner doesn't need it
and the added schema noise would hurt planner quality.
- Throws on unknown parameter types so catalog typos (e.g. "array"
instead of "string") fail loudly instead of coercing silently.
- Preserves enum constraints; drops the enum key entirely when absent
so Gemini doesn't reject empty-enum function-declarations.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the Quiz CRUD surface for the AI agent. Five new tools:
- update_quiz (propose) — rename/archive/pin + description/category
- update_quiz_question (propose) — text, type+options, explanation;
rejects a type swap without a matching optionsJson
- delete_quiz_question (propose) — symmetric to add_quiz_question
- get_quiz_questions (auto) — lets the planner see existing questions
before appending more (avoids duplicates)
- get_quiz_stats (auto) — attemptCount / avgScore / bestScore /
lastAttemptAt; enables adaptive missions like "analyze my weak spots
and generate harder questions"
delete_quiz deliberately left out — too destructive to leave in the
AI's hands when the user can delete manually in two clicks.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Quiz is now an AI-accessible module. The agent can mint empty quizzes
and append questions across all four types (single / multi / truefalse
/ text) via a single add_quiz_question tool whose optionsJson payload
shape is documented in the catalog description. list_quizzes (auto)
returns decrypted metadata so the planner can reference existing
quizzes when extending them. Enables missions like "baue ein Quiz aus
meinen Notizen zu Thema X" — planner reads via list_notes, proposes
create_quiz, then N × add_quiz_question.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `app-registry/types.ts` now includes `tips` in the inline help shape,
matching `ModuleHelp` and what `AppPage.svelte` actually renders.
Drops 3 recurring type errors.
- `event-scout` template's `{ kind: 'daily' }` cadence now carries the
required `atHour` / `atMinute` fields (daily 08:00). Drops the 4th
type error — svelte-check is clean.
- `apps/mana/CLAUDE.md` gains a "Scene Scope" section documenting the
pattern: wire `filterBySceneScopeBatch` in the query AND render
`<ScopeEmptyState>` from the empty branch, so users always see why
the list is empty.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add discover_events (auto) and suggest_event (propose) to shared-ai
tool catalog. discover_events reads the discovery feed, suggest_event
creates a proposal to save a discovered event to the user's calendar.
- Add Event-Scout agent template with daily "Events der Woche" mission.
Policy: discover_events=auto, suggest_event=propose, all else denied.
- Add frontend tool implementations in events/tools.ts — discover_events
calls the feed API, suggest_event delegates to discoveryStore.saveEvent.
- Add feedback.ts — computes implicit user profile from save/dismiss
history (category affinity + source quality as 0–2x weight multipliers).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New module providing weather data for the DACH region via three sources:
- Open-Meteo (DWD ICON-D2 model) for current conditions and 7-day forecast
- DWD warnings endpoint for severe weather alerts
- Rainbow.ai / Open-Meteo fallback for minute-level rain nowcast
Includes API proxy with in-memory caching, Svelte 5 UI with location
picker, hourly/daily forecast, alert cards, and precipitation bar chart.
Two AI tools (get_weather, get_rain_forecast) enable the companion to
answer weather questions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Six P2 items from the AI Workbench audit:
#7 Prompt ↔ loop budget sync:
System prompt now says "1 bis 5 Schritte pro Planungsrunde, bis zu 5
Planungsrunden" — matches MAX_REASONING_LOOP_ITERATIONS. Cross-ref
comment added to runner.ts.
#9 SceneHeader: useAgents() → useAgent(id):
Only loads the single bound agent instead of the full agent list.
Eliminates unnecessary Dexie churn on every scene header render.
#10 Unified scope filter:
New scope-filter.ts with filterByScopeTagMap() (batch, sync) and
filterByScopeAsync() (per-record). Both scope-context.ts (AI) and
scene-scope.svelte.ts (UI) now import from the shared module —
zero duplicated filter logic.
#11 Research dedup:
Research input ID changed from `news-research-${Date.now()}` to
`news-research-${mission.id}` — re-runs overwrite instead of
appending duplicates.
#12 Kontext injection policy clarified:
loadAgentKontextAsResolvedInput no longer falls back to the global
singleton. Comment + code aligned: kontext injection is explicit
(via input picker), not auto. Dead loadKontextAsResolvedInput
kept for potential future opt-in auto-inject feature.
Audit doc updated with all items marked DONE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add a guardrail system that runs alongside the Mission Runner pipeline
to catch obvious issues before they waste tokens or corrupt data.
Architecture (packages/shared-ai/src/guardrails/):
- types.ts: Guardrail, GuardrailResult, 4 phase interfaces
- builtin.ts: 4 built-in guardrails (always active):
- input-size-limit: blocks >100K chars of resolved input
- plan-step-limit: blocks plans with >25 steps (runaway planner)
- duplicate-destructive-tool: warns if undo_drink called 2x
- empty-required-params: blocks create_task without title
- runner.ts: runPrePlanGuardrails/runPostPlanGuardrails/runPreExecuteGuardrails
Wired into runner.ts at 3 checkpoints:
- Before deps.plan() — pre-plan check
- After plan received — post-plan check
- Before each stage() call — pre-execute check
Guardrails are synchronous, never hit the network, and produce
clear error messages when they block.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Connects the existing global tag system (@mana/shared-tags, 15+ module
junctions, TagSelector UI) to the AI agent model so different agents
can operate on different slices of the user's data.
Core additions:
1. Agent.scopeTagIds — optional array of global tag IDs. When set,
the agent sees only records tagged with at least one of those tags
(plus untagged records, which stay globally visible). Empty/undefined
= General-Agent, sees everything. Agent-editor grows a <TagSelector>
under "Bereiche (Tag-Scope)".
2. Per-agent kontext documents — new Dexie table `agentKontextDocs`
(v22, encrypted, synced). Each agent can have its own markdown
context doc, replacing the global singleton auto-inject. Runner
tries agent kontext first, falls back to global singleton when
the agent has no dedicated doc.
3. Ambient scope context — `withAgentScope(tagIds, fn)` sets a
module-level scope during the reasoning loop. Auto-tools read it
via `getAgentScopeTagIds()` and filter their result sets.
`filterByScope(records, getTagIds)` is the reusable filter
primitive (keeps untagged records, drops mismatched tagged ones).
4. Notes tag junction — `noteTags` table (v22) + `noteTagOps` via
`createTagLinkOps`. Notes was the only major module without
structured tag support. `list_notes` now calls `filterByScope`
so a scoped agent only sees notes tagged with its scope.
Flow: mission starts → runner resolves owning agent → reads
agent.scopeTagIds → wraps entire reasoning loop in withAgentScope →
list_notes (and future list_tasks etc.) auto-filter → planner sees
only scope-relevant records → proposes scoped edits.
Runner tests: 8/8. shared-ai type-check: clean.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce AI_TOOL_CATALOG in @mana/shared-ai as the single source of truth
for all 29 tool schemas (17 propose + 12 auto). Both the webapp policy and
the server-side mana-ai planner now derive their tool lists from the catalog
instead of maintaining independent hardcoded copies.
- New: packages/shared-ai/src/tools/schemas.ts — catalog with ToolSchema type
- Rewrite: proposable-tools.ts — derived from catalog instead of hardcoded array
- Rewrite: services/mana-ai/src/planner/tools.ts — 277→30 lines (imports from catalog)
- Simplify: webapp policy.ts — derives AUTO/PROPOSE from catalog defaultPolicy
Adding a new tool now requires 2 files instead of 3-5:
1. Add schema to AI_TOOL_CATALOG (shared-ai)
2. Add execute function in the module's tools.ts (webapp)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Enable real-time token streaming during the planner "calling-llm" phase
so the user sees live progress ("empfange Plan… 128 tokens") instead of
a static spinner. The parser still receives the full text once complete —
no partial-JSON risk.
Changes:
- Extract shared SSE parser from playground into @mana/shared-llm/sse-parser
- remote.ts: use stream:true when onToken callback is provided
- AiPlanInput: add optional onToken field (shared-ai)
- ai-plan task: pass onToken through to backend.generate()
- runner.ts: throttled (500ms) phaseDetail updates during streaming
- Playground: refactored to use shared SSE parser
Also includes: AI agent architecture comparison report (docs/reports/)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Two major tool expansions — the Recherche-Agent and Today-Agent can
now research the web autonomously (no browser needed), and a future
Meeting-Prep agent can read + create contacts.
=== research_news (server-side execution) ===
The biggest addition: mana-ai can now call mana-api's news-research
endpoints (POST /discover + /search) directly, without a browser.
Infrastructure:
- services/mana-ai/src/planner/news-research-client.ts — full HTTP
client with discover→search pipeline. 15s/30s timeouts. Graceful
null on any failure (network, mana-api down, bad response) so the
tick never crashes from research errors.
- config.manaApiUrl added (default http://localhost:3060); wired in
docker-compose.macmini.yml as http://mana-api:3060 + depends_on
mana-api with service_healthy condition.
Pre-planning research step (cron/tick.ts):
- Before the planner prompt is built, the tick checks if the
mission's objective or conceptMarkdown matches research keywords
(same RESEARCH_TRIGGER regex the webapp uses). When it matches:
* NewsResearchClient.research(objective) runs discovery + search
* Results are injected as a synthetic ResolvedInput with id
'__web-research__' and a formatted markdown context block
* The Planner then sees real article URLs/titles/excerpts and can
reference them in create_note / save_news_article steps
* Log line: "pre-research: N feeds, M articles"
Tool registration:
- research_news added to AI_PROPOSABLE_TOOL_NAMES + mana-ai tools.ts
with params (query, language?, limit?). This lets the planner also
explicitly propose a research step as a PlanStep (in addition to
the pre-planning auto-injection).
=== create_contact ===
- Added to AI_PROPOSABLE_TOOL_NAMES + mana-ai tools.ts with params
(firstName required, lastName/email/phone/company/notes optional).
- Contacts are encrypted at rest; server planner can plan the step
but execution stays on the webapp (same as all propose tools).
Full server-side contact resolution via Key-Grant is a future
enhancement.
- get_contacts added to webapp AUTO_TOOLS so agents can inspect
existing contacts without nagging (read-only, auto-policy).
Module coverage now:
✅ todo (5) ✅ calendar (2) ✅ notes (5) ✅ places (4)
✅ drink (3) ✅ food (2) ✅ news (1) ✅ journal (1)
✅ habits (3) ✅ news-research (1) ✅ contacts (1)
11 modules, 28 tools total (17 propose, 11 auto).
Tests: mana-ai 41/41 (drift-guard passes), shared-ai type-check
clean, webapp svelte-check 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes the three biggest tool-coverage gaps so the shipped agent
templates can actually do their job end-to-end. Before this, the
Recherche-Agent couldn't create notes (only edit), the Today-Agent
couldn't create journal entries, and no habit-related tool was
server-proposable at all.
shared-ai (proposable-tools.ts):
- create_note (notes) — key unlock: Recherche-Agent now creates
per-source notes and the summary report.
- create_journal_entry (journal) — key unlock: Today-Agent proposes
a poem as a journal entry with optional mood.
- create_habit (habits) — agent can suggest new habits.
- log_habit (habits) — agent can log a habit completion for today.
Organized the list with per-module section comments for readability
now that we're at 15 proposable tools.
mana-ai (planner/tools.ts):
- 5 new tool definitions with full parameter schemas:
* create_note (title, content?)
* create_journal_entry (content, title?, mood? enum)
* create_habit (title, icon, color)
* log_habit (habitId, note?)
- Drift-guard contract test passes (41/41) — confirms the mana-ai
tool list is in sync with the shared-ai canonical set.
Webapp (policy.ts):
- get_habits added to AUTO_TOOLS (read-only; agent can inspect
which habits exist without nagging the user for approval).
- list_notes added to AUTO_TOOLS (was already used in the reasoning
loop but missing from the explicit auto-list; the planner default
fell through to 'propose' which was wasteful for a read op).
Module coverage after this change:
✅ todo (5 tools) ✅ calendar (2) ✅ notes (5 incl. create)
✅ places (4) ✅ drink (3) ✅ food (2)
✅ news (1) ✅ journal (1) ✅ habits (3)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Closes out T1 with three templates per category as discussed. The
gallery now renders agent-templates and workbench-templates as two
distinct labeled sections — the earlier implicit "everything's a
template for an agent" framing is gone.
Seed handlers (new):
- apps/mana/apps/web/src/lib/modules/habits/seed.ts — title-based
idempotency (there's no description column on LocalHabit). If a
non-deleted habit with the same title exists, the seed is skipped.
- apps/mana/apps/web/src/lib/companion/goals/seed.ts — title-based
idempotency on companionGoals where status !== 'abandoned'.
- Both pulled in via side-effect imports in missions/setup.ts so the
handler registry is populated before any apply.
New templates:
- 🏋️ Fitness (wellness) — scene body/habits/stretch/sleep + 3 habit
seeds (Täglich 30min Bewegung, 3× Woche Training, 2L Wasser) + 1
goal seed (3 Workouts pro Woche). No agent.
- 💻 Deep Work (work) — scene todo/calendar/notes/times + 2 habit
seeds (1 wichtigste Aufgabe pro Tag, 4h Deep Work pro Tag) + 1
goal seed (20h Deep Work pro Woche). No agent.
Gallery two-section layout:
- Title "Templates" (not "Agent-Templates") — broader framing.
- Section 1: "🤖 Agent-Templates" — filters ALL_TEMPLATES where
category ∈ {'ai','delight'}: Recherche-Agent, Kontext-Agent,
Today-Agent.
- Section 2: "🎨 Workbench-Templates" — filters to the rest:
Calmness, Fitness, Deep Work.
- Each section gets a short intro paragraph so users understand the
distinction before scanning the cards.
- Cards themselves unchanged; rendering extracted into a
{#snippet templateCard(t)} shared between both sections.
- Per-category arrays computed once at module-load time (const in
<script>); no per-render filter cost.
Result: each section has 3 templates, categorised by "does this
create an AI agent" rather than by use-case. Keeps the separation
honest — Agent-Templates set up autonomous work; Workbench-Templates
set up the user's own workspace.
Tests: shared-ai 26/26, webapp svelte-check 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First pass of the workbench-templates plan (docs/plans/workbench-
templates.md) — templates are no longer agent-centric but a general
"starter kit" bundle: optional agent + optional scene + optional
missions + optional per-module seeds. Pilot non-AI template "Calmness"
ships alongside.
Shape generalisation (packages/shared-ai/src/agents/templates/types.ts):
- AgentTemplate renamed to WorkbenchTemplate; all fields now optional
(agent, scene, missions, seeds). Back-compat AgentTemplate alias
kept so research/context/today keep compiling.
- Added `category: 'ai'|'wellness'|'work'|'lifeEvent'|'delight'` +
`icon` (for non-agent templates that have no avatar) + `version`
field (for future update-detection).
- New WorkbenchTemplateSeedItem shape: `{stableId?, data: unknown}`.
Module-specific seed payloads are typed at the handler side.
- Existing three AI templates nachgezogen: category='ai' (or
'delight' for today-agent), icon, version='1'.
Seed infrastructure:
- apps/mana/apps/web/src/lib/data/ai/agents/seed-registry.ts — in-
memory handler map keyed by module name; module-local seed.ts files
register themselves at import time.
- apps/mana/apps/web/src/lib/modules/meditate/seed.ts — first handler:
createPreset-based, idempotent via stableId embedded as HTML
comment in the preset description (T1 pragmatism; T2 adds a proper
column on the preset schema).
- data/ai/missions/setup.ts pulls `import '$lib/modules/meditate/seed'`
so the handler is registered before any template is applied.
Applicator upgrades (data/ai/agents/apply-template.ts):
- Agent step now optional — skipped cleanly when template has no
agent part.
- New step 4: seeds. Walks template.seeds, looks up the handler for
each module, aggregates per-item outcomes (created/skipped-exists/
failed) into result.seedOutcomes. Missing handler = warning, not
fatal. Crypto/encryption unchanged — seeds go through the same
module stores that module code already uses.
- Result shape gains `seedOutcomes: Record<string, SeedOutcome[]>`
so the gallery can show "3 new, 1 already there".
Calmness pilot (packages/shared-ai/src/agents/templates/calmness.ts):
- category='wellness', NO agent, scene with meditate/mood/journal/
sleep apps, two meditate preset seeds:
* 4-7-8 Atmung (breathing preset)
* Body-Scan 10min (bodyscan preset with 9 scan steps)
- Each seed has a stableId so re-apply is idempotent.
Gallery updates (routes/(app)/agents/templates/+page.svelte):
- Card avatar falls back to t.icon when no agent. "Agent" chip shows
only for agent-templates; "N Seeds" chip shows for templates with
seeds.
- Detail header shows "Workbench-Setup ohne AI-Agent" when no agent.
- New "Seeds" preview section: lists per-module counts + item names.
- Options section gains a "Seed-Daten in Module einpflegen" checkbox.
- Success panel shows seed summary: "3 Seeds neu, 1 bereits
vorhanden".
Tests: shared-ai 26/26, webapp svelte-check 0 errors, 0 warnings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Debug log from a "tag 4 notes" mission showed the planner's second-round
response truncated mid-step: it was proposing one add_tag_to_note per
listed note but ran out of tokens halfway through note #2. Parser
rejected the malformed JSON → loop exited with 0 staged, user saw
nothing to approve.
Raising maxTokens to 4096 fits ~15-20 step objects, which covers the
batch-tagging / batch-save pattern the reasoning loop is designed for.
Also updating the system prompt so the planner actually knows about
the loop it's running inside: read-only tools are announced as
auto-executing with outputs visible next turn, and a new rule makes
explicit that batch jobs must emit all write-steps in one plan (because
staging a propose-tool ends the turn). Step count raised 1-5 → 1-10.
Prompt snapshot tests still pass (they check structure, not text).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
First pass of the Multi-Agent discoverability UX. A new /agents/
templates route showcases pre-configured agents; clicking one creates
agent + scene + starter mission(s) as a single bundle. Addresses the
"blank form anxiety" + "user doesn't know what agents are for"
observations from the UX brainstorm.
Three templates for v1 (shared-ai/src/agents/templates/):
- 🔍 Recherche-Agent — reads sources one by one, writes a note per
source, summarizes into a report. Manual-cadence mission; all
writes propose so user curates.
- 🧭 Kontext-Agent — learns about the user via a weekly check-in.
Reads kontext/notes/goals, asks 2-3 questions, proposes a diff-
style context update. Weekly Sunday cadence.
- 🌅 Today-Agent — researches "on this day" history each morning,
writes a 4-8 line German poem, proposes a journal note. Daily 7am
cadence. A "delight" agent, not a productive one.
Each template packs (agent config, scene layout, starter mission):
- AgentTemplate type lives in @mana/shared-ai — pure data, no runtime
imports. Adding a new template = drop a file in templates/ and
extend ALL_TEMPLATES.
- Template-specific policies derive from the proposable-tool list so
drift-guard catches divergence from the canonical set.
- Starter missions default to startPaused=true — user sees the
mission ready-to-go and hits Play when ready. Prevents surprise
autonomous work on first apply.
Applicator (data/ai/agents/apply-template.ts):
- Creates agent → scene (if template defines one) → missions in
order. Agent failure = abort; scene/mission failures surface as
warnings in the result without blocking.
- Duplicate-name handling: falls through to findByName, returns
existing agent with wasExisting=true; scene is skipped in that
case to avoid clone-proliferation.
Gallery page /(app)/agents/templates/+page.svelte:
- Three large cards side-by-side (stacks on mobile) with avatar /
label / tagline / meta chips (Scene, N Missionen).
- Click opens detail panel with full description, scene preview
(app-ids + widths), mission preview (title / objective / cadence),
and override checkboxes (create scene, create missions, start
active vs paused).
- Success panel shows what landed with warnings inline; CTA back to
workbench.
Discoverability in /ai-agents module:
- Bar now has two buttons: "Aus Template" (primary, goto templates
route) + "Eigener Agent" (secondary, opens the existing blank-form
create mode).
- When only the default "Mana" agent exists, render a dashed promo
banner at the top linking to the template gallery. Disappears as
soon as the user has a second agent.
Tests: webapp svelte-check 0 errors, 0 warnings. shared-ai 26/26.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Makes the "read all notes and tag them #Natur/#Technologie/…" use case
fully functional. Four new ModuleTool entries in notes/tools.ts:
- list_notes(limit?, query?, includeArchived?) — auto, read-only. Returns
id + title + excerpt so the planner can reference concrete notes
without dumping full bodies.
- update_note(noteId, title?, content?) — proposable. Destructive full
overwrite. Docstring nudges toward append_to_note when applicable.
- append_to_note(noteId, content) — proposable, non-destructive. Handles
the trailing-newline separator so markdown stays clean.
- add_tag_to_note(noteId, tag) — proposable, idempotent, case-insensitive.
Strips leading #, replaces spaces with _, skips if already present.
Exactly the categorization primitive the user asked for.
All three writes are added to AI_PROPOSABLE_TOOL_NAMES so both the
webapp policy and mana-ai's boot-time drift guard agree (now 11 tools).
Mirrored in services/mana-ai/src/planner/tools.ts.
AiProposalInbox mounted on /notes so approvals land inline in the
notes module too (already appears in the mission-detail cross-module
inbox via the earlier commit).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Second phase of the Multi-Agent Workbench rollout (docs/plans/
multi-agent-workbench.md). Builds on Phase 1's identity-aware Actor.
Adds the Agent primitive — a named AI persona that owns Missions,
carries its own policy + memory, and (from Phase 3 on) drives the
Workbench lens. Everything is wired; a single user currently has one
"Mana" default agent until the UI (Phase 5) lets them create more.
Shared types (@mana/shared-ai):
- agents/types.ts: Agent, AgentState, DEFAULT_AGENT_ID/NAME constants
- policy/types.ts: AiPolicy + PolicyDecision (moved from webapp so
Agent.policy can reference it without a runtime dep on the web app)
- missions/types.ts: new optional Mission.agentId field
Webapp data layer:
- data/ai/agents/{types,store,queries,bootstrap}.ts
- Dexie schema v19 adds `agents` table (indexes on state, name,
[state+name]); sync registered under the existing ai app-id
- Encryption registry: agents.systemPrompt + agents.memory encrypted;
name/role/avatar/policy stay plaintext for search + UI rendering
- DuplicateAgentNameError thrown at write time (not a Dexie unique
index — bootstrap races between tabs would otherwise hit
ConstraintError; store now resolves via getOrCreateAgent)
- bootstrap.ts: ensureDefaultAgent + backfillMissionsAgentId. The
backfill runs once per device (localStorage sentinel) so missions
that pre-date the rollout get stamped with the default agent's id.
Called fire-and-forget from startMissionTick() during layout init.
Runner threading (already merged into d5c351d63 via Till's debug-log
commit that picked up my uncommitted edits):
- runner.ts + server-iteration-staging.ts now resolve mission.agentId
to the real Agent and build makeAgentActor with agent.name as
displayName. Missing-agent fallback keeps using LEGACY_AI_PRINCIPAL
so historical writes still attribute cleanly.
Tests: shared-ai 26/26, mana-ai 35/35, svelte-check 0 errors.
Agent store vitest suite is present but blocked by a pre-existing
\$lib alias resolution issue in the webapp vitest config that
predates this phase (proposals/store.test.ts is broken the same way
on HEAD). Will address separately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New local-only Dexie table _aiDebugLog (v20, never synced) holds one
row per mission iteration with the full system+user prompt, raw LLM
response, latency, every ResolvedInput the planner saw, and pre-step
state (kontext-injected? web-research-ok-or-error?). Capped at 50
newest rows.
aiPlanTask always returns the captured prompt/response on AiPlanOutput.
debug; the runner persists it only when isAiDebugEnabled() — toggled
via a checkbox in the Mission detail header (defaults to on in DEV
builds, off in prod, override via localStorage 'mana.ai.debug').
New <AiDebugBlock> component renders below each iteration card:
expandable sections for Pre-Step, Resolved Inputs (each input
individually collapsible), System Prompt, User Prompt, Raw Response,
plus a "📋 JSON" copy-to-clipboard button for bug reports.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>