Changelog¶
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.2.19] - 2026-08-01¶
Findings from a full audit of the template's product code. The theme is one pattern: a mechanism is built and correct, and nothing attaches it. Rate limiting shipped twice and guarded no route; the reranker was warmed at startup and thrown away on every request; the delegated-auth mode was half-wired. All three lived in configurations no CI job rendered — 53 of 88 CLI flags were never generated — so nothing was ever in a position to notice.
Security¶
- Delegated auth left local self-registration mounted, and the first account created is an app-admin —
--auth-mode delegatedmeans identity belongs to the IdP, andget_current_useraccepts nothing but IdP signatures.auth.pywas never told: it still generated/auth/login,/auth/register,/auth/refreshand/auth/logout, backed by the local password column. On a fresh delegated deployment theuserstable is empty until an IdP user first authenticates, andUserService.registerpromotes the first row torole=admin+is_app_admin=True— while SQLAdmin'sAdminAuthauthenticates on a local password and never consults the IdP. So between deploy and first real sign-in, an unauthenticated stranger could POST/auth/registerand then log straight into/adminwith full CRUD over every table (the panel is live fordevelopment/local/staging, and.env.exampleshipsENVIRONMENT=development). Delegated mode now generates only/auth/me;AdminAuthadditionally requiresis_app_adminand, in delegated mode, an IdP-linked row - The chat WebSocket validated tokens against the wrong authority in delegated mode —
get_current_userbranched on the auth mode;get_current_user_ws, thirty lines below it in the same file, always calledverify_tokenagainst the localSECRET_KEY. Both directions were wrong: an IdP-issued token was rejected, so chat never connected in a delegated deployment, and a token minted by the still-mounted/auth/loginwas accepted, so the account above could drive the agent and read the knowledge base. The WebSocket now uses the same authority as the REST API, with thetypeclaim check confined to the local branch — an external IdP does not mint our access/refresh distinction - Rate limiting was generated, documented, advertised, and enforced on nothing — two complete implementations shipped: the slowapi limiter in
core/rate_limit.pyand a Redis sliding-window service inservices/rate_limit/, the latter with a per-IP auth rule of 5-per-15-minutes already written down.main.pysetapp.state.limiterand the 429 handler but never addedSlowAPIMiddleware, no route carried@limiter.limit, andmake_rate_limit_dephad zero call sites outside its own docstring./auth/loginaccepted unlimited attempts, each burning a bcrypt verification. The service is now attached to/auth/login,/auth/register,/password-reset/requestand/magic-link/request; the slowapi copy and its dependency are gone.make_rate_limit_depgained an anonymous sibling because the original requiredCurrentUserandActiveOrg— which is precisely why the auth rule could never be wired to a pre-auth endpoint services/rate_limit/could not be imported at all in two common configurations —service.pyimportedActiveOrgfromapp.api.deps, which only exists with--teams, andstorage.pyimportedget_redisfromapp.core.cache, a function that does not exist in that module and a file that--no-cachingdeletes. Both are now gated correctly, and the limiter opens its own Redis connection fromREDIS_URLrather than borrowing one it cannot reach from outside a request- Password-reset and magic-link tokens were replayable for their full TTL —
create_password_reset_token's docstring said "Single-use JWT" and nothing consumed it: nojti, no denylist, nopassword_changed_atanywhere in the app. A link that leaked once — forwarded mail, a shared support inbox, a logging proxy — could be replayed for the rest of the hour, including after the legitimate user had used it, which locks them out of the account they just recovered. Reset tokens now carry a digest of the password hash they were issued against, so the reset itself invalidates them, stateless and with no new table. Magic links carry amagic_link_epochthat redemption increments, which invalidates the redeemed link and every other one outstanding for that user - Connector secrets crashed on any RAG project without a Telegram or Slack bot —
sync_source.pyencrypted withsettings.CHANNEL_ENCRYPTION_KEY, a setting only declared when a messaging channel is enabled, so creating or reading a sync source raisedAttributeError. It now prefers that key when it exists (so rows already encrypted with it keep decrypting) and falls back toSECRET_KEY, which is what every other caller ofapp.core.cryptouses
Fixed¶
- The cross-encoder reranker reloaded its model inside every search request — the lifespan built a
RerankService, calledwarmup()(which loadsms-marco-MiniLM-L6-v2into memory) and stored it instate["rerank_service"]; nothing ever read it back.get_retrieval_serviceconstructed a freshRerankServiceper request, andCrossEncoderRerankerholds its model on the instance and loads it lazily — so each RAG search paid the model load again, seconds of CPU and ~90 MB, with N concurrent searches holding N copies.get_embedding_serviceandget_vectorstoreboth already checkedrequest.state; the reranker was the one that was missed. Also declaredrerank_serviceonLifespanState, which the assignment had been violating - Reading a webhook returned 500 until it had been modified at least once —
WebhookRead.updated_atwas declared non-optional, butTimestampMixingivesupdated_atanonupdateand no default, so a freshly created row carries NULL and response validation failed. Nowdatetime | None, matching the sharedTimestampSchema - Delegated auth was broken outright without
--oauth—get_or_create_from_idpwas nested inside theenable_oauthgate, soget_current_usercalled a method that did not exist and every authenticated request raisedAttributeError. It is now gated on the auth mode that uses it - Four of the five
AgentSessionvariants treated any unrecognised control frame as a prompt — the shareduse-chat.tshook emits{type:"resume"}and{type:"ask_user_response"}regardless of which framework was generated, and only the PydanticAI copy ignored frames it did not implement. On the others those frames fell into the start-a-turn branch and answered the user with "Empty message". Each variant now declares the frames it handles, so the sibling behaviour is identical by construction rather than by five copies happening to agree - A Redis failure inside the rate limiter raised
TypeErrorinstead of logging —logger.error("...", error=str(exc))passes a keywordloggingdoes not accept, in the one branch that only runs when Redis is already broken
Added¶
- CI renders the configurations these bugs were hiding in — four new jobs: delegated auth in both JWKS and shared-secret modes (with the admin panel on, since that is the combination that escalated), rate limiting with and without teams, and RAG with a cross-encoder reranker. The reranker job asserts the dependency reads lifespan state, because
tyreports that class of mistake as a warning and exits 0 - Rate-limit tests that assert a 429 comes back — the previous test was
assert limiter is not None, which an unattached limiter passes. There are now tests for the per-IP limit firing, limits being scoped per IP, and/auth/loginactually returning 429 on the sixth attempt, plus an autouse fixture that gives each test its own counters - Replay regression tests for both single-use links, including two outstanding magic links where redeeming the newer one must invalidate the older
Changed¶
- The generator no longer installs the generated project's dependencies —
celery,taskiq,arq,stripe,pytest,pytest-asyncio,httpxandpydantic-settingswere declared as runtime dependencies offastapi-fullstackand imported by none offastapi_gen.uvx fastapi-fullstackwas pulling roughly 20 MB of site-packages — stripe alone is 13 MB — for a tool that renders Jinja templates. Moved to the dev group;ruff(shelled out to by the post-gen hook) andemail-validator(needed byEmailStr) stay - Removed three modules that were written and never wired:
core/csrf.py(CSRFMiddlewarewas never added to the app; HTTP auth is Bearer-header based and the BFF's cookies arehttpOnly+SameSite=Lax, so it was protecting nothing and the README claimed otherwise),api/versioning.py(no call sites), andcore/rate_limit.py - Routes no longer reach into repositories —
knowledge_bases.pyandorg_integrations.pyboth calledsync_log_repodirectly, against the layering rule in the generated project's own.claude/rules/architecture.md. AddedSyncSourceService.list_logs - Corrected four documentation claims that described code that does not exist: the root
CLAUDE.mdadvertised user-scopedsk_<43>API keys (there is one globalAPI_KEY),AGENTS.mdtold agents to runmypy(the project usesty),core/crypto.py's usage example named the wrong key — following it silently produces rows nothing can decrypt — and the README listed CSRF protection
[0.2.18] - 2026-08-01¶
Fixed¶
- Every generated project with pagination failed to start —
fastapi-pagination0.15.16 probes for FastAPI's private_get_body_field/_get_flat_body_paramshelpers to decide which signature to call, and itsImportErrorfallback binds the publicget_body_fieldwhile still setting_get_body_field_new_signature = True. On any FastAPI below 0.140.5 — which the template'sfastapi>=0.135.3,<0.137pin guarantees —add_pagination(app)therefore calls it with abody_paramskwarg it does not accept, andapp.mainraisesTypeErroron import. Nothing in the generated project was wrong; the release landed 2026-07-28 and took every template CI job with it. Pinnedfastapi-pagination<0.15.16until upstream fixes the fallback or the FastAPI pin moves past 0.140.5 - The DeepAgents backend was constructed with an argument it no longer takes —
deepagents0.7 dropped the runtime parameter fromStateBackend.__init__and changedcreate_deep_agentto accept a backend instance rather than a factory, solambda rt: StateBackend(rt)was wrong twice over:tyrejected the call, and the first agent invocation would have raisedTypeError._create_backendnow returnsStateBackend()typed asBackendProtocol, and the dependency is capped<0.8— that API has moved between minors, and unbounded pins turn an upstream release into a broken generation RedisClient.getleaned on atype: ignorethat stopped applying —connect()passesdecode_responses=True, but redis-py's annotation cannot express that and still admitsbytes, so the declaredstr | Nonewas a claim the type checker had no reason to accept. The value is now decoded explicitly instead of being asserted through a suppression- Chat was dead on any Docker deployment that wasn't localhost — the
frontendservice indocker-compose.prod.ymlpassed nobuild:args at all, andNEXT_PUBLIC_*is inlined into the browser bundle bynext build, so the image baked the Dockerfile'sws://localhost:8000/http://localhost:8000defaults and no runtimeenvironment:entry could change them. Every visitor's browser then dialledws://localhost:8000on its own machine: connection refused,isConnectedfalse, chat input permanently disabled with the status stuck on "Offline". Both compose files now pass the fullNEXT_PUBLIC_*set as build args, defaulting tohttps://api.${DOMAIN}/wss://api.${DOMAIN}behind a proxy and to${PUBLIC_HOST}with the published ports without one, each overridable per variable (#132) BACKEND_WS_URLwas documented everywhere and read nowhere — a leftover from when the chat stream went through a Next proxy. It sat infrontend/.env.example,frontend/README.md, the rootREADME.md, theMakefileand the generated.env.local, so the one variable a deployment could set for the socket was the one variable with no effect; the browser readsNEXT_PUBLIC_WS_URL. Removed, along withNEXT_PUBLIC_AUTH_ENABLED, unread since auth stopped being optional. The frontend env table now says which side reads each variable and thatNEXT_PUBLIC_*needs a rebuild (#132)- Auth cookies were
Secureover plain HTTP, so every request after login 401'd — the flag was hardcoded toNODE_ENV === "production"in six route handlers, and a browser silently discards aSecurecookie that arrives on anhttp://origin. Serving the app on a LAN IP or a bare Docker host therefore looked like a successful login — the user and access token come back in the response body — and then/api/auth/meanswered 401 forever, so the access token was never refreshed and the chat socket never reconnected with a good one. NewCOOKIE_SECUREenv var: unset followsNODE_ENV,falseopts an HTTP deployment out, an unrecognized value falls back to theNODE_ENVdefault so a typo cannot downgrade production. The six copies of the cookie block are now onelib/auth-cookies.tshelper with unit tests (#132) - nginx never upgraded the chat WebSocket — the
Upgrade/Connectionheaders were set onlocation /ws, but the endpoints are mounted under the versioned prefix (/api/v1/ws/agent,/api/v1/ws/projects/...), so the handshake fell through tolocation /and failed. The location is now/api/v1/ws, and the same wrong path is fixed in the deployment docs (#132) - The white-label brand vars had the same build-arg gap —
NEXT_PUBLIC_BRAND_COLORandNEXT_PUBLIC_BRAND_LOGO_URLare read by the browser but were never declared as Dockerfile args, so a Docker build withenable_brand_from_configbaked in undefined values while the variables looked settable at runtime (#132)
Added¶
- "Serving from a host that isn't localhost" section in the generated
docs/deploy.md— the build-time-vs-runtime split ofNEXT_PUBLIC_*and theSecure-cookie-over-HTTP trap are the two things that break a first non-localhost deploy, and neither failure names its own cause (#132)
[0.2.17] - 2026-07-25¶
Added¶
- MCP client — new
enable_mcp_clientflag (off by default, PydanticAI + PostgreSQL only): end users connect external Model Context Protocol servers from Settings → Integrations and the agent picks up their tools. Ships a curated marketplace catalog (Notion, Linear, Jira/Confluence, Stripe, GitHub, Zapier, Exa, CoinGecko, …) with brand logos baked in as data URIs, per-user connections with Fernet-encrypted tokens, a connectivity test that lists the server's tools so the user can pick which ones the agent may call, and a per-chat "Plugins" tab to toggle them. Auth is either a static bearer token or one-click OAuth 2.1 (authorization-code + PKCE, RFC 9728/8414/7591 discovery and dynamic client registration), over both streamable-HTTP and SSE transports. Deployment-wide servers are pinned viaMCP_SERVERS. Every URL the flow touches — including each redirect hop and every endpoint the remote server advertises — goes through the same SSRF policy as webhooks. Tools are prefixed per server, and an unreachable or unauthorized server is skipped for that turn instead of failing the chat. CLI:--mcp-clientoncreate+ wizard step (#124) - Version-upgrade tooling —
fastapi-fullstack upgradepulls template improvements into an already-generated project without losing local edits. It renders the template at both the generated-from and target versions using the answers stored in.fastapi-fullstack.json, normalizes all three trees the way generation did (ruff check --fix+ruff format, Prettier on the frontend) so formatting can't read as an edit, and runs a real 3-way merge onto atemplate-upgrade/v<version>branch. Structural changes between releases (renames, removals, breaking notes, manual steps) are declared inUPGRADES.yaml, with aRename guardCI job that fails any PR moving a template file without recording it.upgrade recoverbootstraps a manifest for projects generated before this existed. Documented indocs/guides/version-upgrade.md(#114)
Fixed¶
- MCP tools were missing on the channel path —
agent_invocation.pyis the second place the PydanticAI agent is built (Slack/Telegram), and it never received the user's MCP toolsets. A connection configured in Settings worked in web chat and silently did nothing in a channel, with no error on either side.build_toolsets_for_usernow takes an optionaluser_id, so channel traffic with no mapped account still gets the deployment-managedMCP_SERVERS(#130) - Two chat turns could both spend the same OAuth refresh token — providers that rotate refresh tokens invalidate whichever copy is redeemed second, after which the connection stops working with nothing to point at. The refresh now happens under
SELECT ... FOR UPDATE; the losing turn re-reads the row and uses the token the winner stored (#130) - An abandoned OAuth consent redirect stayed redeemable forever — the
statetoken is the only thing authenticating the callback, and it travels through the provider and the browser's history. A pending flow now expires afterFLOW_TTL_SECS, and a pending payload that can't be decrypted (rotatedSECRET_KEY) asks the user to start again instead of raising out of the route as a 500 (#130) - A bad or duplicated
MCP_SERVERSname silently dropped a server from every chat turn — the name is that server's tool prefix, so two servers sharing one prefix meant only the first was ever attached, visible nowhere but a log line. Names are now validated as slugs and checked for duplicates at startup, and the.env.examplesuggestsgithub-internal, which can't collide with the marketplace'sgithub(#130) - The MCP client had no CI job — the integration matrix ran only
ruff+tyagainst an MCP render, so the generated project's MCP test suite and the frontendtsc/eslintnever executed for that configuration. AddsTemplate - PostgreSQL + MCP Client, which renders with--slack(the only build containingagent_invocation.py) and runs both the backend and frontend suites (#130) - The plugin marketplace fetched 14 favicons from Google on every settings page view — the logos were already baked in as data URIs for the offline demo export; the Settings UI now uses those, so the app doesn't tell a third party which plugins its users are browsing. A catalog entry whose name a workspace server already claims now reads "Provided by your workspace" instead of offering a Connect button that would create a connection dropped on every turn, and editing a connection sends only the fields that changed, so renaming one no longer resets its last-checked status (#130)
- A file replacing an untracked symlink landed on disk but never on the branch — the untracked-symlink exemption exists so a stray link where the template now ships a file doesn't abort the run, but it was subtracted from the staging set too. The link was replaced, the file was written, and
git addskipped it: a silently partial upgrade, with the new file showing as untracked on a branch that claimed to be complete.materializenow keeps the two sets apart — tracked symlinks are the "never deleted, never restaged" guarantee, untracked ones only waive the collision guard tar -xread$TAPEinstead of the merged tree — the extraction ran with no-f, so tar falls back to the archive named by$TAPE(or a compiled-in default device). With that variable exported the whole extraction became a silent no-op — exit 0, nothing written — and the upgrade then staged the old content as if the merge had produced it. Nowtar -x -f -- A broken symlink hid an untracked-file collision — the guard used
exists(), which follows the link, so a dangling one read as absent and was overwritten without warning..gitignorekeeps such a link out ofls-files --otherstoo, so nothing else caught it either. The guard now useslexists - The rename guard still failed open on one path — a 404 was treated as "no published baseline yet" for either PyPI call, so a 404 fetching the wheel of a version PyPI says exists skipped the guard entirely and an unrecorded rename shipped on a green build. Only failing to resolve the latest version can mean "nothing published"; a failed template fetch is now exit 2
- The rename guard printed a block its own stale-check would reject — the suggested
- version:came fromget_generator_version(), which mid-cycle is the baseline, and the half-open(from, to]range filters those straight back out. Pasting the suggestion turned an "uncovered" failure into a "stale" one. It now emits a<next-release>placeholder plus the reason whenever the working tree isn't already bumped - A transient PyPI blip silently downgraded the wheel download to unverified — the digest was fetched in a second metadata request, and any failure there returned
None, which the caller reads as "no digest known". One request now carries both the URL and its sha256 - Prettier's exit code is checked, like ruff's. Prettier formats what it can parse and exits non-zero on the rest, so one tree hitting an unparseable file came out formatted differently from the other two — and the "did it run" return value couldn't carry that, so the evenness warning stayed silent
- A malformed
UPGRADES.yamlblock is rejected where the file is read, naming the file and the offending entry. Three consumers indexr["from"]/r["to"]directly, so a hand-edit typo used to surface as a bareKeyErrorfrom whichever release script happened to run read_manifestrejects a non-objectcontext. It is the one key every consumer indexes into, so a hand-edit that turned it into a list got past the presence check and failed several modules downstream- Recovery lost the generation timestamp, so a legacy project's first upgrade conflicted on files nobody touched —
generated_atis stamped intobackend/pyproject.tomland into every alembicCreate Date:header, andnormalizeblanks it in all three trees so it cancels out — but only when it knows the value. A recovered manifest never carried one, sostrip_generated_atwas a no-op, BASE and THEIRS rendered the stamp empty while the client's files carried the real one, and every stamped file read as an edit they never made. Measured on a minimal project: 3 of 208 files differed with zero client edits, one of thembackend/pyproject.toml— which moves on nearly every release, so the guide's flagship "recover a legacy project" flow opened with a conflict. Alone among the value variables this one leaves a trace, so recovery now reads it back out ofbackend/pyproject.toml, and warns loudly when it can't - Prettier skipped the one tree that already had it —
format_frontendbailed out whenever the tree already containedfrontend/node_modules, which is exactly OURS when the client committed theirs and never the freshly rendered BASE/THEIRS. That is the uneven-formatting case the previous fix added a warning for; the install sitting in the tree is a perfectly good one, so it is now used in place (and left alone afterwards) instead of being a reason to skip - Directories left empty by a removal are pruned. git tracks files, not directories, so a release that dropped a whole subtree unlinked its files and left the folders behind for good —
git checkoutprunes them, hand-rolled deletion has to say so. A directory still holding an untracked or ignored file survives _iter_text_filesprunes skipped directories instead of filtering them one entry at a time.rglobcannot prune, andrestore_generated_atruns over the client's whole repo, wherenode_modulesalone is routinely six figures of entries- Upgrading a project that wasn't its own git repo deleted the project — nothing checked that the target directory was the repository root, and the two halves of the merge disagree about what a path means anywhere else:
checkout-indexbuilds OURS from index paths (repo-root-relative) whilels-treerun throughgit -C <subdir>emits cwd-relative ones. For a project atmonorepo/myappthe rendered BASE/THEIRS therefore shared no path with OURS, every template file read as a client deletion, andmaterializeunlinked the client's real files and staged the deletions — with no error and a report claiming the template had removed them.upgradenow refuses up front (dry runs included, since the preview is wrong the same way) - The rename guard could fabricate a rename and then pass itself — CI honoured the
removed:/waived:waivers recorded inUPGRADES.yaml, butscripts/record_renames.pyonly looked at its own--waiveflag. A maintainer's intentional delete+add was therefore re-detected and written back as a rename, after which CI went green because the move was "covered" — and the client's next upgrade moved their copy of a deleted file onto an unrelated path. Both halves now read the samerecorded_waivers() - Stale directory renames slipped past the release guard — the stale-version check only matched exact
(from, to)entries, so a file covered by a directory renamea/ → b/was neither uncovered nor stale. If that entry was recorded under a version<=the baseline, the half-open range dropped it and the whole subtree degraded to delete+add — the highest-stakes case, since one entry covers many files. The check now resolves the covering entry and reads the version from it - Uneven formatting across the three trees was undetectable — the merge is only sound if BASE, OURS and THEIRS are formatted identically, and both formatters already returned whether they had run, but
normalize_treediscarded the answer.format_frontendbails out when a tree already containsfrontend/node_modules— true for OURS whenever the client committed theirs, never for the freshly rendered BASE/THEIRS — so Prettier ran on two trees out of three and every.tsxfile read as a client edit.normalize_treenow returns aFormattersRunand the upgrade warns when the three disagree - Two harmless, common outcomes were reported as "Other changes (review on the branch)" — a file the client deleted while the template still ships it, and a file both sides deleted. Neither needs any action, and the label read as "the upgrade lost my file". They now have their own report line ("You deleted these") and are folded into "Already converged" respectively
upgrade finalizeraised a bareKeyErroron a hand-edited.fastapi-fullstack.json.pendingthat was missingpackage_version; it now fails with a message that says what to delete. A corrupt (non-JSON) pending file gets the same treatment- The "invalid version" error from the template fetcher said target version even when it came from the manifest's
package_version— which is"UNKNOWN"after a recovery that couldn't read the README footer, sending the user to look at a--tothey never passed make upgrade-finalizedidn't pass$(ARGS)through, so--pathwas unreachable frommake-
.fastapi-fullstack.json.pending/.candidateare now gitignored in generated projects, so the "commit your resolved conflicts" step can't sweep the pending manifest into the commit -
Upgrade read a third of the backend as edits you never made — the post-gen hook runs
ruff check --fixandruff formatat generation time, but the upgrade's normalization ran onlyformat. OURS therefore arrived with unused imports stripped and# ruff: noqaheaders removed while the freshly rendered BASE/THEIRS still carried them, so 36 of 127 backend files in a stock PostgreSQL project differed with zero client edits in them. The report labelled them "Kept your changes (template unchanged)", and the next release touching any of their import blocks would have conflicted instead of auto-updating.normalize_treenow takesrendered=Truefor BASE/THEIRS and runs the autofix pass on them only — never on OURS, whose code must not be rewritten into the merged result. Measured on the same project: 36 spurious differences → 1 (the real edit), and the report goes from 30 false "Kept your changes" to 8 honest "Auto-updates" - Generation and upgrade crashed under a non-UTF-8 locale — the post-gen empty-file sweep,
is_stub_file, and the__init__.pycheck all read rendered files with the platform default encoding. The template ships 209.md/.mdx/.ts/.tsxand 163.pyfiles with non-ASCII content, so on Windows cp125x or aLC_ALL=Ccontainer generation died withUnicodeDecodeError— and so did rendering BASE/THEIRS during an upgrade. All three now read as UTF-8 through a sharedread_texthelper upgrade --path X finalizetold the user to do the thing that had just failed — the misplaced-flag guard advised "put it before the subcommand", which is exactly the form it rejects.finalizeandrecovercarry their own--path, so the message now points atupgrade finalize --path ...; flags with no subcommand equivalent still say to drop it- RAG disabled left a broken frontend — generating a project with RAG off and Teams on kept the org "Integrations" screen (
orgs/[id]/integrations) and its API proxy, while the post-gen hook removed every module they import (@/lib/rag-api,@/components/rag/*,use-org-integrations), sonext buildfailed on four missing modules. The page and proxy are now removed with the rest of the RAG frontend, and the button linking to them is gated inorgs/page.tsx— the backend drops/api/v1/org/integrationsin that configuration anyway (#128) - Code-execution tool broken against pydantic-monty 0.0.19 — the sandbox dependency was pinned
>=0.0.18, and 0.0.19 replaced the one-shotMonty.acreate()/run_async()API with a worker pool (AsyncMonty→checkout()→session.feed_run()) and dropped themax_allocationsresource limit. Generated projects withenable_code_executiontherefore failedty checkand would have raisedAttributeErroron the firstrun_pythoncall.code_execution.pynow uses the session API, the pin is>=0.0.19, andCODE_EXECUTION_MAX_ALLOCATIONSis replaced byCODE_EXECUTION_MAX_MEMORY_MB(default 256, mapped to themax_memorylimit in bytes) - Empty feature-gated files shipped in generated projects — the post-gen stub sweep only walked
backend/app/**/*.py, so a doc or frontend module whose whole body sits behind a feature conditional was written out as an empty file instead of being removed:docs/howto/add-rag-source.md,add-sync-connector.md,configure-sync-sources.mdwith RAG off, plus four dead frontend modules. The sweep now covers.md,.mdx,.ts, and.tsxas well (#128)
[0.2.16] - 2026-07-17¶
Added¶
- Self-contained HTML conversation export — new
enable_demo_exportflag (off by default, requires a frontend):frontend/demo-export/is a Vitesinglefilebuild target that bundles the REAL replay UI (DemoReplay → MessageItem → Recharts, plusglobals.css) into one HTML file, with tiny local shims aliasing the Next.js-only imports (next/image,next/dynamic,next/navigation,next/link,next-intl).scripts/export_demo_html.pymerges a saved conversation (from/api/v1/demos/<id>, any URL, or a JSON file) into that bundle and writes a single offline.htmlreplay — no server, no API key, no network; open it and press Play. Supports--theme light|dark|system,--title, and--avatar(embedded as a data URI). CLI:--demo-exportflag oncreate+ wizard checkbox (#118) - Reasoning trace persistence — migration
0025adds a nullablethinkingtext column tomessages; the PydanticAI/PydanticDeep sessions collectThinkingPart/ThinkingPartDeltacontent during streaming and persist it with the assistant turn, so loaded conversations and the HTML export show the THINKING block, not just live streams. Exposed through the message schema, repository, service, and the frontendRawMessage/ConversationMessagetypes (#118) - Expanded demo replay — "Agent's computer" side panel with a per-step log and run-graph view (sequential action nodes; deep research fans out into its parallel subagents), step scrubber with prompt-by-prompt navigation, pause/resume support in the replay engine, per-step live timer, and a collapsible per-prompt step timeline in the dock. New
fetch-urltool-result renderer (gated onenable_web_fetch), relevance scores + full snippets in the detailed web-search view, subagent findings in the detailed research view, and aformatSqlhelper that pretty-prints single-line SQL (#118) - Shared assistant-turn builder —
chat-containernow reusesbuildAssistantParts(the same builder the demo replay uses), so thinking, reconstructed deep-research blocks, and tool/text parts render consistently between the authenticated chat and the public demo (#118)
[0.2.15] - 2026-07-03¶
Fixed¶
- Webhook tables migration — generated projects with
enable_webhooksshipped theWebhook/WebhookDeliverymodels but no Alembic migration, soalembic upgrade headnever created thewebhooksandwebhook_deliveriestables — they were absent at runtime and SQL admin failed to load them. Adds0024_create_webhook_tablesfollowing the repo's conditional-migration pattern: whenenable_webhooksis on it creates both tables with their indexes (theuser_idcolumn and index gated onuse_jwtto mirror the model), and when off it renders a no-op revision with the samerevision/down_revisionso the chain keeps a single head with no gap (#113)
[0.2.14] - 2026-06-23¶
Added¶
- Public demo gallery & live replay — any conversation can be flagged as a demo by an admin (
PATCH /api/v1/admin/conversations/{id}/demo), after which it appears on a public gallery page (/{locale}/demo) and can be played back frame-by-frame at/{locale}/demo/{id}. The gallery shows a deterministic waveform-trace visualisation (WaveTrace) unique to each demo UUID alongside a message-count, title, and preview quote. The replay page streams every message turn in real time, including tool calls and their results, at a configurable speed — exactly as the original session unfolded. No auth required; the public endpoints (GET /api/v1/demos,GET /api/v1/demos/{id}) return only flagged conversations (#106) - Conversation replay engine —
useConversationReplayhook drives step-by-step playback: reveals committed messages one turn at a time with character-by-character streaming on the active turn, emits atickon every visual update, and exposesstart / stop / isReplaying / displayMessages / progress.useStepReplayhandles the per-turn streaming loop with configurable char-rate and inter-message delay. Tool calls animate in the same pass, with human-readable captions mapped from tool names viaagent-step-captions.ts(#106) is_democonversation flag — Alembic migration0023,Conversation.is_demo: bool = Falsecolumn, repository helpers (list_demos,get_public_demo), and service methods (mark_as_demo,list_public_demos,get_public_demo). Admin conversations endpoint extended with a toggle action andis_demofield in the response schema (#106)- Admin conversations panel update — the admin conversations table gains a "Demo" toggle button that calls the new action endpoint and reflects the current state with an optimistic UI update and toast feedback (#106)
- Demo UI — cinematic pre-play overlay (pulsing branded play button with radial glow, backdrop blur), sticky progress bar, "Jump to active" re-engage button, and "Watch again" reset. Gallery uses a dark hero with ambient glow blobs, live-indicator pill, and a two-column card grid with per-card hover gradient reveal (#106)
Fixed¶
- Demo replay auto-scroll — replaced
window.scrollBy(which has no effect when the page's scroll container is notwindow) with an inneroverflow-y-autocontainer that the replay hook scrolls directly viacontainer.scrollBy. The outer wrapper usesh-[calc(100vh-3.5rem)](fixed height) soflex-1on the messages pane actually constrains its height and triggers overflow — previouslymin-hlet the container grow unbounded, makingoverflow-y-autoa no-op (#106) - Jinja2 parse error on demo pages — JSX inline style objects (
style={{ ... }}) conflicted with Jinja2's{{ }}delimiter, causing "expected token 'end of print statement', got ':'" at generation time. Both demo template files are now wrapped in{% raw %}...{% endraw %}, with the single{{ cookiecutter.backend_port }}substitution broken out of the raw block. Inline style objects are also extracted to named constants to eliminate allstyle={{occurrences (#106)
Changed¶
- Demo replay scrollbar — replaced the browser-default scrollbar on the replay container with a 4 px thin variant: transparent track,
border-coloured thumb at rest, brand-coloured thumb on hover. Applied via Tailwind arbitrary-variant selectors (webkit) andscrollbar-width: thin/scrollbar-color(Firefox) (#106)
[0.2.13] - 2026-06-22¶
Added¶
- Prefect background-task option (
--background-tasks prefect,use_prefect) — a fourth task backend alongside Celery, Taskiq, and ARQ. Unlike the Redis-backed queues, Prefect runs its own orchestrator: aprefect-servercontainer (UI on:4200) plus aprefect-runnerthat registers deployments fromapp/worker/prefect_app.pyand polls for work. Ships flows for RAG (on-demand ingest/sync + a scheduledcheck_scheduled_syncs), billing/email reminders (trial-ending, low-credits), and credits maintenance (ledger cleanup,mv_usage_dailyrefresh) on cron/interval schedules. Self-hosted by default (PREFECT_API_URL); setPREFECT_API_KEYfor Prefect Cloud (#105) - Organization integrations / sync sources UI — manage RAG connectors per organization from the dashboard (
/orgs/[id]/integrations). Add a Google Drive or S3/MinIO source, assign it to a knowledge base, trigger a sync manually, and inspect per-run logs (status, mode, duration, and ingested/updated/skipped/failed counts). Sources can live at the org level as reusable templates or be wired to a specific collection. Connector credentials are encrypted at rest with Fernet (app/core/crypto.py) and masked in API responses. Endpoints under/api/v1/org/integrations(admin/owner only) (#105) - Billing, credits & usage metering — a full Stripe billing stack: plans/prices mirrored locally, seat-based subscriptions with trials and end-of-period cancellation, an idempotent Stripe webhook event log, a Customer Portal link, and invoices. Adds a credits ledger (
credit_transaction) and per-message usage events (usage_event: input/output/cached tokens, model, provider, credits charged) rolled up into amv_usage_dailymaterialized view. New billing dashboard pages: subscription, payment methods, invoices, credits balance/ledger, and usage with daily credits/calls and by-model token charts (#105) - File view & download — preview knowledge-base documents and chat attachments in-app via a modal viewer supporting 20+ types (PDF, images, audio/video, CSV tables, HTML sandbox, JSON, Markdown, syntax-highlighted code, plain text) with a download/open-external fallback for anything else. Backed by document/file download routes on the API (#105)
- Standalone TODO planner and subagents (
enable_todo,enable_subagents) — the planner and multi-agent delegation that previously only shipped with Deep Research are now independent opt-in toggles. Enablingenable_deep_researchstill turns both on automatically (#105) - Chat UI overhaul — specialized tool-result cards replace generic JSON dumps: numbered web-search results, knowledge-base chunks grouped by source file with relevance scores,
run_pythoncode + stdout/result, skill cards,ask_userquestion/answer transcripts, and datetime cards. Adds a live subagent feed and side panel (status, messages, results per delegated agent), a sources panel that collects every citation (knowledge base + web) with clickable[N]badges in Markdown, a sticky task/plan checklist above the composer, inline chart rendering (bar/area/line/pie/scatter, theme-aware), file-preview cards for attachments, and a reasoning/"thinking" view (#105) - Multi-provider embeddings — the embedding provider is now selected automatically from the chosen LLM provider: OpenAI →
text-embedding-3-small, Anthropic → Voyage, Google → Gemini (multimodal text + image) (#105) - Marketing & dashboard polish — new marketing sections (feature bento, comparison table, integrations grid, enterprise-security band, outcomes band, case study, smooth scroll), dashboard sparkline stat cards and a usage timeline chart, an admin message-ratings chart, and a
/dev/componentsshowcase page. New shared UI primitives: data table, confirm dialog, form field, icon button, section heading (#105) - Claude Code skills in generated projects — a feature-gated
.claude/skills/toolkit of model-invoked playbooks grounded in the project's conventions:alembic-migration,pytest-suite,agent-tool(adapts to the chosen AI framework),frontend-feature,rag-knowledge,background-task(adapts to the chosen queue),billing-stripe, andchannel-bot. Only the skills matching the selected stack are emitted (e.g. nofrontend-featurefor backend-only projects). Complements the existing.claude/commands/and.claude/rules/
Changed¶
- Database simplified to PostgreSQL — PostgreSQL (async) is now the single supported database. The migration history, seed command, and Makefile targets are PostgreSQL-only; SQLModel and the example-CRUD scaffold simply require PostgreSQL now (#105)
- Charts consolidated — the separate
enable_antv_chartsoption is gone; charting is covered byenable_charts(native chart tool + web rendering) (#105)
Removed¶
- CrewAI framework — the generator now offers 5 AI frameworks (PydanticAI, PydanticDeep, LangChain, LangGraph, DeepAgents). CrewAI's older
opentelemetry-sdkpin conflicted with current Logfire, and the option is dropped (#105) - MongoDB and SQLite database backends —
--databasenow acceptspostgresqlornone. Projects needing those engines should pin a generator ≤ 0.2.12 (#105) enable_antv_chartsflag (folded intoenable_charts) (#105)
Fixed¶
- Frontend Docker build (
make dev-frontend) failed with"/app/public": not found— the standalone runner stage copiesfrontend/public, but Next.js never created the directory when there were no static assets. Shipfrontend/public/.gitkeepso the directory always exists in the build context (#103, #105)
[0.2.12] - 2026-06-17¶
Added¶
- Deep Research mode (
enable_deep_research,--deep-research, PydanticAI only) — turns the assistant into a deep-research agent: a TODO planner (pydantic-ai-todo), parallel researcher/analyst/writer subagents (subagents-pydantic-ai), and an automatic context manager (summarization-pydantic-ai). The planner clarifies scope, plans the work, delegates web research to subagents, and composes a cited report; progress streams to a dedicated live research panel (plan checklist, subagent status cards, context-usage meter) while the final report streams back as a normal message. TODO state persists in PostgreSQL when available, else in memory. Activated at runtime withENABLE_DEEP_RESEARCH=true; a client can opt a single turn out withdeep_research=false. Gated behind the new flag and PydanticAI-only by a config validator — when off, the template generates exactly as before. Ships with a stop control and a per-turn research store in the web chat (#90)
Fixed¶
make installfailed withFailed to spawn: pre-commit— dev tools (pytest,ruff,ty,pre-commit, …) lived under[project.optional-dependencies].dev, which uv only installs via its deprecateddev-extra special-casing; on uv versions whereuv sync --devtargets the PEP 735 group instead, they were skipped entirely. Moved them to[dependency-groups]souv sync/uv sync --devinstall them deterministically and--no-dev(prod Dockerfile) still excludes them (#95, #101)make docker-db(and the otherdocker-*/docker-prod-*/docker-redistargets) erroreddocker-compose: No such file or directory— those recipes shelled out to the legacy Compose v1 binary while the dev/quickstart targets already used Compose v2. Every recipe now invokesdocker compose; thedocker-compose.*.ymlfilenames are unchanged (#96, #100)- Frontend Docker build (
make dev-frontend) failed to build the image — the Dockerfile copied abun.lockb*glob that never matched the current textbun.lock(sobun install --frozen-lockfilehad no lockfile and errored), the healthcheck shelled out to acurltheoven/bunimage doesn't ship, and theNEXT_PUBLIC_*client vars were never passed as build args. Copiesbun.lock*, passes the public env vars as build args (Dockerfile + compose), probes the healthcheck with bun'sfetch, chowns the copiedpublic/, and guardsparseLoadSkillResultsobun run buildtype-checks (#97, #99) - PydanticDeep OpenAI models are now routed to the OpenAI Responses API (#93)
- Taskiq worker and scheduler containers stay healthy; the taskiq-only worker regression test is removed from celery/arq/none projects so it doesn't linger as an empty file (#94)
ty checkon generated projects — the admin/user routes for SQLite were syncdefs that neverawaited the always-asyncUserService, so they returned un-awaited coroutines (real bug); they are nowasync. The service's id parameters are typed per database (UUIDfor Postgres,strfor SQLite/MongoDB) via aUserIdalias, the agent's capability list is typedlist[Any], the admin-stats best-effort model imports are feature-gated instead of importing absent modules, and theAdminServicesession is typedAnyso one implementation serves async/sync. A minimal SQLite project now type-checks clean (0 diagnostics)
Dependencies¶
- Pinned
fastapi>=0.135.3,<0.137in generated backends — FastAPI 0.137 made prefix-lessinclude_routerreject the documented@router.get("")empty-path idiom, breaking every generated project at import (#90) - Added
pydantic-ai-todo,subagents-pydantic-ai, andsummarization-pydantic-aito generated backends whenenable_deep_researchis on (#90) - Bumped
codecov/codecov-action6 → 7 in the CI actions group (#89)
[0.2.11] - 2026-06-12¶
Added¶
- AntV advanced-diagram tools + interactive maps (
enable_antv_charts,--antv-charts) — adds anmcp-server-chartDocker sidecar exposing AntV diagram tools (flowchart, mind-map, org-chart, sankey, waterfall, funnel, treemap, radar, histogram, boxplot, dual-axes) and a nativecreate_maptool (Leaflet/OpenStreetMap) with a typedMapMarkerschema that prevents empty-marker validation errors from weaker models.create_mapis wired into all 6 agent frameworks; the AntV diagrams render server-side via the sidecar. Web chat renders maps withreact-leaflet(MapMessage/MapLeaflet) and AntV diagrams as images in the tool-call card. Opt-in and profile-gated —ENABLE_ANTV_CHARTS=falseby default, sidecar started withdocker compose --profile antv up -d; for prod self-host GPT-Vis-SSR viaANTV_VIS_REQUEST_SERVERinstead of AntV's public render backend (#83) ask_usertool (PydanticAI) — the agent can pause a run to put one or more questions to the user and resume with their answers, for intake/setup flows and mid-run clarifications. Backed by a WebSocket pause/resume inAgentSessionand an interactive multi-stepQuestionPromptcard in the frontend (numbered options, free-form answers, skip). System-prompt guidance steers the model to use it only when a missing detail would genuinely change what it does next (#88)run_pythoncode execution (enable_code_execution,--code-execution, PydanticAI only) — arun_pythontool backed by thepydantic-montysandboxed interpreter. In one tool turn the model can compute projections/aggregations and callcreate_chart/create_map/current_datetimedirectly from inside the sandbox; visualizations created in-code stream to the session as live, persisted interactive cards (the sametool_call/tool_resultpair as a direct call). Restricted stdlib (math,asyncio,json,datetime,re); activated at runtime withENABLE_CODE_EXECUTION=true. Temporary shim until PydanticAI's officialCodeExecutionToolsetships (#88)- Skills system (
enable_skills,--skills, PydanticAI only) — apydantic-ai-skillsSkillsToolsetthat loadsSKILL.mdfiles frombackend/skills/as agent tools (the model picks a skill, then follows its instructions). Ships the loader only — drop your own skills in; the toolset no-ops when the directory is empty. Frontend rendersload_skill/list_skillstool calls as clean skill cards. Pairs with code execution for skills that compute (#88)
Fixed¶
- MCP connection leak in CrewAI —
get_antv_crewai_toolsnow memoizes the started MCP adapter so it's started once per process, not once per request (#83) - Per-request event-loop blocking in LangChain/LangGraph/DeepAgents — AntV tool discovery is memoized so
_run_synconly blocks on the first request (#83) - antvis-chart Docker healthcheck — plain
wgetwas rejected by the streamable-HTTP endpoint; replaced with anodeone-liner. Added coordinate-bounds validation toMapSpec._validate_center(matching per-marker validation) and removed the deadparse_map_specexport (#83) - Streamed tool-call args could be a raw string —
agent_sessionnow usesargs_as_dict(raise_if_invalid=False)so tool-call cards always receive a dict; a stray/duplicateask_user_responseframe (e.g. after a reconnect) is dropped instead of surfacing a spurious "Empty message" error (#88)
Changed¶
- Generated-project ruff tests run via
uvx—test_template_integration.py/test_message_ratings.pyinvokeuvx rufffrom the project'sbackend/dir (matching the post-gen hook) instead ofuv run ruff, avoiding a strayVIRTUAL_ENVbreaking local runs. New matrix configspydantic_ai_code_executionandpydantic_ai_skillsso both new paths are linted + type-checked in CI (#88)
Removed¶
ai_agent_test/generated snapshot — removed the 710-file generated project that was committed to the repo root; it only served to confuse Renovate and tooling (thetemplate/source is the durable artifact). Added to.gitignore
Dependencies¶
pydantic-monty>=0.0.18andpydantic-ai-skills>=0.11.0added to generated backends whenenable_code_execution/enable_skillsare on;react-leafletadded to the template frontend and anmcp-server-chartsidecar to the compose files whenenable_antv_chartsis on
[0.2.10] - 2026-05-27¶
Added¶
- Frontend
PageHerocomponent — sharedcomponents/dashboard/page-hero.tsx(151 lines) used by admin, billing, settings, KB, organizations, and dashboard pages so headers share a single typographic rhythm and breadcrumb pattern instead of each page hand-rolling its own - Chat controls panel — new
components/chat/chat-controls.tsx(586 lines) consolidates model picker, knowledge-base toggles, and conversation settings into one panel. Replaces the splitchat-settings.tsx(deleted) andkb-selector.tsx(deleted) — fewer UI surfaces, no more duplicate KB lookups - Auth screens redesigned — login, register, reset-password, and forgot-password forms now use a split-screen layout (product pitch on the right, form on the left), proper OAuth divider, and consistent form spacing. Generated
(auth)/layout.tsx+magic-link-sent/page.tsxupdated to match
Changed¶
- Marketing site refreshed — hero, pricing teaser, final CTA, marketing footer rebuilt with cleaner typography, monthly/annual toggle on pricing, and configurable footer columns (
footer-config.ts) - Knowledge bases page redesigned —
kb/kb-list.tsxrewritten (266-line refactor) with card layout, per-base actions, and clearer empty-state copy - Dashboard / admin / settings / billing pages — every page header migrated to the new
PageHero; layout, sidebar, and command palette tightened. Globals CSS adjusted for the new spacing scale - README — banner image at the top replaced with the live chat demo video so the first thing a visitor sees is the product running; merged the separate "web search" and "chart generation" demos into one (the new demo covers both); refreshed every product screenshot from
assets/new2/; pruned ~40 unused assets (assets/new/,assets/chat/, old marketing PNGs, button SVGs)
Fixed¶
- No-AI projects (
use_ai=False) failed to build — generator now removes every AI-only surface that previously leaked through and broke imports:conversation/conversation_share/message_rating/user_slash_commandmodels + repos + services, theagent,admin_conversations,admin_ratings, andme_slash_commandsroutes, and the corresponding frontend pages (chat/,admin/conversations,admin/ratings,settings/slash-commands), API proxies, and data hooks. RAG-off projects also drop the KB UI (components/kb,app/api/kb,(dashboard)/kb,use-knowledge-bases.ts,types/knowledge-base.ts) sonext buildno longer fails on orphaned imports - Backend modules unconditionally pulled in chat code —
api/deps.py,api/routes/v1/__init__.py,db/models/__init__.py,repositories/__init__.py,schemas/__init__.py,services/admin.py,repositories/user.py, andadmin.py(SQLAdmin) haduse_database-gated imports ofConversation/Message/MessageRating/etc. that crashed in no-AI projects. Re-gated touse_aiand added literal-zero fallbacks for conversation counts in admin stats / user listings (PG, SQLite, MongoDB) - CLI: Slack/Telegram channels silently accepted without an AI framework —
ProjectConfigvalidation now rejects--slack/--telegramwhenuse_aiis off (the channel adapters only exist to relay messages toAgentInvocationService), with a quick-fix message temperatureforwarded to reasoning models (gpt-5.5, o1) — those models reject the parameter entirely, soAssistantAgentno longer falls back tosettings.AI_TEMPERATURE; it staysNoneand is only forwarded toModelSettingswhen the caller explicitly sets itreranker.pyCohere init imported the SDK only to discard it — replaced thefrom cohere import AsyncClientprobe withimportlib.util.find_spec("cohere")so we check availability without polluting imports- Auth components barrel exported password forms in OAuth-only builds —
components/auth/index.tsnow gates the local-auth form exports behinduse_local_auth, so OAuth-only projects don't ship dead code that references missing endpoints - README demo videos invisible on GitHub —
<video>tags fromraw.githubusercontent.comdon't render in the GitHub README viewer (only<img>autoplays from raw URLs). Converted both chat and RAG demo.mp4s to optimized.gifs (960px, 10fps, palette-quantized — 5.8 MB and 3.0 MB respectively, ~80% smaller than source) and switched the README to<img>tags. Removed the unused.mp4sources - Renovate scanned the generated
ai_agent_test/snapshot — opened useless PRs against the snapshot'spackage.json/docker-compose.ymlinstead of the actualtemplate/source, so every accepted bump would silently revert on the next regeneration. AddedignorePaths: ["ai_agent_test/**", "**/node_modules/**"]so future bumps target the template files
Dependencies¶
- Generator CI Python pinned to 3.14 (was 3.12) —
.github/workflows/{ci,docs,release}.yml(#75) aquasecurity/trivy-action→ v0.36.0 in generated projects' CI (#74)milvusdb/milvusDocker tag → v2.6.17 in generateddocker-compose.{dev,prod,}.yml(#77)qdrant/qdrantDocker tag → v1.18.1 in generateddocker-compose.{dev,prod,}.yml(#78)quay.io/coreos/etcdDocker tag → v3.6.11 (Milvus dependency) in generateddocker-compose.{dev,prod,}.yml(#80)prettier-plugin-tailwindcss→ ^0.8.0 in template frontendpackage.json(#81 bumped only the snapshot; the template source is the durable fix)
[0.2.9] - 2026-05-17¶
Added¶
- Chart generation tool (
enable_charts) — optionalcreate_charttool letting the agent produce line/bar/pie/area/scatter charts. Returns a validatedChartSpec(data, series, custom style/palette/legend/axis) as JSON, so the same payload flows to every surface. Registered in all 6 agent frameworks (PydanticAI, LangChain, LangGraph, CrewAI, DeepAgents, PydanticDeep). Web chat renders it interactively with Recharts; Slack/Telegram get a server-side PNG via matplotlib (charts_channel_png, gated dep, with a markdown-table fallback). Wizard prompt +--chartsCLI flag +enable_chartscookiecutter var - Portable
fetch_urltool (web_fetch_tool) — SSRF-safe "read this web page" tool for LangChain/LangGraph/CrewAI/DeepAgents (which had no model-native web-fetch). Reusesapp.core.sanitize.validate_webhook_url, re-validates every redirect hop, caps size/timeout, extracts readable text (BeautifulSoup). PydanticAI/PydanticDeep keep their nativeWebFetch. Closes the gap whereenable_web_fetchwas a silent no-op for those frameworks - Web Search & Fetch offered for every agent framework in the interactive wizard (was PydanticAI-only); CrewAI now actually attaches
search_web/fetch_urlto the research agent (they were registered but never used) - Gated test suites:
test_chart_tool.py,test_fetch_url.py,test_web_search.py
Changed¶
- Refreshed default AI models — OpenAI →
gpt-5.5, Anthropic →claude-opus-4-7, OpenRouter →anthropic/claude-opus-4-7, multi-provider →openai/gpt-5.5(Google staysgemini-2.5-flash). UpdatedAI_AVAILABLE_MODELS(GPT-5.x frontier line; full Claude Opus/Sonnet/Haiku line) and Claude pricing in billingMODEL_COSTS; synced.env/.env.example/docs - Rewrote the default agent system prompt (outcome-first style) — real personality + answering policy + formatting. The RAG variant is no longer a straitjacket: the agent answers general-knowledge questions directly instead of replying "not in the knowledge base", and treats
search_documentsas a tool to use when relevant with a retrieval budget and citations web_searchtool returns structured JSON (WebSearchResults) instead of ad-hoc text, so the chat UI renders clickable titles, domains and snippets (addedparse_web_search). Fixed stale frontend detection that meant the rich card never showed for LangChain/LangGraph/DeepAgents- Removed the
.env.prod/.env.prod.exampleabstraction — production now uses the samebackend/.envas dev (it already contained every variable.env.prod.exampledefined).docker-compose.prod.ymlreadsenv_file: ./backend/.env;make prodchecks forbackend/.envand passes--env-file backend/.envto Compose..env.prodremoved from.gitignore. Migration: move any values from your old.env.prodintobackend/.env(gitignored) on the server - Chat UI: ordered message timeline — a streamed assistant turn now renders as an ordered sequence of parts (thinking → tools → text → …) in true chronological order instead of three fixed slots, so multi-step turns display correctly. Provider-agnostic; CrewAI keeps its multi-message layout
- Tool-call cards redesigned — collapsed by default to a clickable bar (tool name + input hint, e.g. the query/URL), expand to the formatted view,
</>toggle for arguments + raw output. Chart cards auto-expand (they're only useful when visible). New generic fallback renderer displays any newly added backend tool sensibly with no frontend changes; removed the per-tool status icon
Fixed¶
- LangChain/LangGraph/DeepAgents streaming: tool-call arguments were empty in the web UI — the token-chunk path emitted a premature
tool_callwithargs: {}and poisoned the shared dedup set, suppressing the complete event. Theupdatesstream is now the single source of truth (full args), with no duplicate/empty card - Reasoning ("thinking") now streams reliably — extracted via a shared helper, with a fallback that emits reasoning from the final message for providers that don't stream it as chunks
AIMessageChunkmerge crash —Additional kwargs key created_at … unsupported type <class 'float'>on the OpenAI Responses API. Usage is now summed viaadd_usageinstead of merging whole chunks- DeepAgents tool calls never fired —
_stream_update_eventchecked the graph node"agent", butcreate_deep_agent(deepagents 0.6.1 → LangChaincreate_agent) names it"model". Tool calls, args and reasoning are now emitted for DeepAgents KBSelectorinfiniteGET /api/kbrequest loop when a workspace has no knowledge bases — a length-based effect re-fired after every empty fetch; replaced with a one-shot ref guardtest_agents.pywas not provider-gated — it patched OpenAI-only symbols, breaking generation/tests for Anthropic/Google/OpenRouter (and the LangChain branch). PydanticAI now patches the single_build_modelseam with a realTestModel; LangChain patches the provider-correct chat class
[0.2.8] - 2026-05-11¶
Added¶
- Email module (
enable_email) — Transactional email system with three providers: Resend (async), SMTP (aiosmtplib), and Log (dev/test). Pre-rendered HTML/text templates stored inemails/compiled/using[[variable]]substitution.EmailServicefacade with convenience wrappers for all email types: welcome, password reset, invitation, payment succeeded/failed, trial ending/expired, subscription canceled/changed, low credits, newsletter welcome - Email triggers wired —
UserService.register()firessend_welcome;InvitationService.invite()firessend_invitation; billing webhook handlers fire payment/subscription lifecycle emails using Stripe customer data (fail-open — email errors never break webhook processing) - Stripe billing — rate limiting per plan (
enable_rate_limiting) — Sliding window rate limiter backed by Redis sorted sets (ZADD/ZREMRANGEBYSCORE/ZCARD pipeline) with in-memory fallback.RateLimitRulefrozen dataclass (per_user, per_org, per_ip, configurable periods);RateLimitCategoryconstants; data-driven plan features overrideDEFAULT_RATE_LIMITS;make_rate_limit_dep(category)factory for FastAPIDepends(); admin bypass; fail-open on Redis error; HTTP 429 withRetry-Afterheader - Extended frontend billing dashboard —
SubscriptionPanelwith 4 states (free/trial/active/canceled), cancel/reactivate dialogs, plan details;CreditsPanelwith balance display, low-credit alert, top-up button, transaction history with type badges;/billing/subscriptionpage with live plan cards;/billing/creditspage.useSubscription,useCredits,usePlanshooks added touse-billing.ts - Admin user management —
GET/PATCH/DELETE /admin/users/{id}endpoints (requiresis_app_adminflag).POST /admin/users/{id}/impersonateissues a short-lived (1h) JWT token to act as any user — token is returned in the API response. Frontend:/admin/userspage with search, role toggle, delete, and impersonation-token-to-clipboard button;/admin/page.tsxoverview with navigation cards;useAdminUsershook - UsageService —
app/services/usage.py— recordsUsageEventin DB, computes credits viausage_to_credits(), debits org credits viaCreditService.debit(). All 3 DB variants (PG/SQLite/MongoDB). Wiring point for agent invocations - Usage dashboard —
/billing/usagefrontend page: total credits/tokens/calls KPI cards, recharts bar chart of credits by model, per-model breakdown table, CSV export of credit transaction history - Anomaly detection service (
enable_usage_anomaly_detection) —anomaly_detection.py— spike detection: current-hour credits vs rolling 24h average; alert if ratio > 3×; optional Slack webhook notification (enable_slack_alerts,SLACK_ANOMALY_WEBHOOK_URLsetting) - Newsletter signup (
enable_newsletter_signup) —POST /newsletter/signupendpoint;NewsletterSignupReact component; sends welcome email via email service - Changelog page (
enable_changelog) —/changelogroute with release history, change type badges (feat/fix/improvement/chore) - Pricing comparison page (
enable_comparison_pages) —/pricingroute fetches live plans from API; monthly/annual toggle; plan feature list; trial days display; "Get Started" CTA
Fixed¶
- Email templates excluded by
.gitignore— Renamedemails/dist/→emails/compiled/to avoid the genericdist/gitignore rule silently stripping all compiled templates from generated projects billing/facade.pyimportsusage_event_repounconditionally —app.repositories.usage_eventwas imported in both PostgreSQL and SQLite branches regardless ofenable_credits_system; projects generated withenable_billing=True+enable_credits_system=Falsecrashed on startup withImportErrorfunc.case()SQLAlchemy 2.x crash inmessage_rating_repo—func.case((condition, value), else_=0)raisedTypeError: Function.__init__() got an unexpected keyword argument 'else_'; replaced withcase(...)imported directly fromsqlalchemy- Credits/usage dashboard widgets shown when
enable_credits_system=False—UsageTimelineandTopModelscomponents were gated onenable_billinginstead ofenable_credits_system; they fetch from/billing/me/credits/usage/...endpoints that don't exist without the credits system, producing silent 404s and empty charts - MongoDB projects:
admin.pyused SQLAlchemyfunc.count—AdminServicenow has separate{%- if use_postgresql or use_sqlite %}/{%- elif use_mongodb %}branches; the MongoDB branch uses BeanieDocument.find().count()and returns[], 0for Stripe events (not applicable to MongoDB projects) - Admin
GET /conversations/{id}returned 404 for other users' conversations —get_conversationandlist_messagesroute handlers now resolveuid = None if current_user.role == "admin" else current_user.id;user_id=Nonein the service layer bypasses the ownership check, allowing admins to read any conversation - Frontend
?id=URL param blocked by ownership guard —fetchConversationsremoved theresponse.items.some(c => c.id === urlId)check before loading messages; any?id=value is now attempted unconditionally and a 404/403 from the server clears the ID silently (non-admins are still protected server-side) - Admin conversations page "View" opened an in-page read-only preview — Replaced the custom preview panel with a
Linkto/chat?id=<conversation_id>; admins now land on the full chat UI with the real message history - Admin ratings page "Export" returned 404 —
window.openwas targeting/api/v1/admin/ratings/export(direct backend path) instead of/api/admin/ratings/export(the Next.js proxy route that attaches the auth cookie) - CI integration test failures —
generated_project_fullfixture now setsfrontend=FrontendType.NEXTJS(required whenoauth_provider=GOOGLE); MongoDB repository__init__.pyimport guard split so only SQL-only repos (chat_file_repo) are excluded from MongoDB projects; SQLite auth test fixed a mock type mismatch (MagicMock→AsyncMock)
[0.2.7] - 2026-04-26¶
Fixed¶
- Disabled features no longer leak generated files —
post_gen_project.pynow removes channel adapters/routes/services/repos/schemas/models/commands/migrations whenuse_telegramanduse_slackare both off, RAG sync infrastructure (sync_log,sync_source,rag_documentfiles) when RAG is off, DeepAgents project scaffolding when not selected, leftover test stubs for disabled modules, and emptydocker-composeplaceholders when Docker is disabled agent.py(DeepAgents): undefinedfile_ids— WebSocket payload parsing now extractsfile_ids = raw_data.get("file_ids", [])before use (previously raisedNameErrorat runtime)agent.py(CrewAI): missingConversationUpdateimport — Added to imports so title-update path no longer crashesrag.py:complete_synccalled on wrong service — Was invoked onSyncSourceService; now correctly routed toRAGSyncServiceIngestionService()instantiated without required args — AddedIngestionService.from_settings()classmethod factory; routes/commands/workers now use itmessage_rating_reponot exported — Added toapp/repositories/__init__.py; admin ratings flow no longer fails on importConversationService.get_conversation_with_messagesmissing on SQL backends — Previously only existed on MongoDB; added to PostgreSQL + SQLite variantsProjectService.list/ChannelBotService.listshadowed builtinlist— Renamed tolist_for_user()/list_all()(withfind_active()andlist_by_platform()helpers added)ModuleNotFoundError: No module named 'app.rag.connectors'—post_gen_project.pywas deleting the entirerag/connectors/directory when neither Google Drive nor S3 ingestion was enabled, butsync_source.pyalways importsCONNECTOR_REGISTRYfrom that package. Directory is now preserved; only the individual connector files are removed- Empty
tasks/channel.pygenerated for projects without Telegram or Slack — Added removal to thenot use_telegram and not use_slackpost-gen hook block - Frontend TypeScript strict mode errors — Fixed 26
noUncheckedIndexedAccessandundefined-assignability violations across 7 files:rag/page.tsx(array index + file loop guard),chat-container.tsx(model selectoruseStatetype + fallback),chat-input.tsx(speech recognition result guard),tool-approval-dialog.tsx(editedArgsindex fallback),tool-call-card.tsx(regex capture group fallbacks),breadcrumb.tsx(route segment index),theme-toggle.tsx(persisted zustand state hydration)
Changed¶
- Strict layered architecture enforced across the template — Routes call services only (via FastAPI
Depends); services call repositories only; repositories are the sole layer permitted to talk to the database. Worker tasks, channel adapters, CLI commands, and webhook handlers no longer perform raw DB operations admin_conversations.pyrewritten to useConversationSvc.admin_list_with_users()andUserSvc.admin_list_with_counts()telegram_webhook.py/slack_webhook.pyuseChannelBotSvcviaDependsinstead of opening their own DB sessions (bot_service.find_active(bot_id))agent.pyusesconv_service.list_attached_files(file_ids)instead of rawselect(ChatFile)queries- Worker tasks (
rag_tasks._run_ingestion,_run_sync,_update_status,_update_sync_log) refactored onto the service layer - CLI commands (
commands/channel.py,seed.py,rag.py) refactored with a_channel_service()context-manager helper - All
self.db.execute/commit/addremoved from services; sessions auto-commit viaget_db_session/get_db_context/get_worker_db_context - Repositories expanded with the queries services now need —
conversation.admin_list_with_users()+export_chunk()(3 backends);user.list_query()+admin_list_with_counts()+delete_non_admins()+has_any()(3 backends);chat_file.get_many()+link_to_message();message_rating.get_user_ratings_for_messages()+get_rating_counts_for_messages()+get_ratings_with_users_for_messages();webhook.create_delivery()+save_delivery();sync_log.create(sync_source_id=...);rag_document.delete_by_collection() - Routes thinned to HTTP-only layer — All response-object construction moved from route handlers into services:
admin_ratings.py: CSV/JSON export helpers (_csv_escape,_csv_row_values,_serialize_csv_row,_validate_export_format,_export_disposition,_json_export_response,_stream_csv_sync/async) andexport_ratings()moved toMessageRatingService. Route reduced to a singlereturn await rating_service.export_ratings(...)callsessions.py:SessionReadconstruction inlist_sessionsmoved toSessionService.list_sessions()which now returnsSessionListResponsedirectlyrag.py:SyncSourceRead(...)construction moved toSyncSourceService._to_read()(used bylist_sources,create_source,update_source);RAGSyncLogItem(...)moved toRAGSyncService.list_sync_logs()→ returnsRAGSyncLogList;RAGTrackedDocumentItem(...)moved toRAGDocumentService.list_documents()→ returnsRAGTrackedDocumentList;RAGDocumentItem(...)moved toBaseVectorStore.get_document_list();ConnectorInfo/ConnectorListconstruction moved toSyncSourceService.list_connectors()oauth.py: Three-step find/link/create OAuth flow extracted toUserService.get_or_create_oauth_user()(all 3 DB variants);google_callbackreduced to a single service callusers.py: RawAnnotated[User, Depends(RoleChecker(UserRole.ADMIN))]replaced withCurrentAdminalias throughout;Depends(get_current_user)replaced withCurrentUser- Unused
current_user/admin_userroute parameters that only provided auth enforcement renamed to_: CurrentAdmin/_: CurrentUseracross all affected routers agent.py: inline imports moved to module level —from datetime import datetime, UTC,import json,from pydantic_ai.messages import BinaryContent,from app.services.file_storage import get_file_storage, and pydantic_deep session/project service imports were scattered across WebSocket handler bodies; all moved to the top of their respective framework blocksconversations.py: Direct field mutationdata.user_id = current_user.idreplaced withdata = data.model_copy(update={"user_id": current_user.id})(Pydantic v2 safe update); inlineConversationShareSvcimport moved to module level; section-divider comments (# Message Rating Endpoints,# Sharing endpoints) removed
Security¶
- Generator dependency floors raised —
pyproject.tomlruntime/dev/docs floors bumped to currently-used versions:click>=8.3.0,cookiecutter>=2.7.0,rich>=15.0.0,questionary>=2.1.0,pydantic>=2.13.0,pydantic-settings>=2.13.0,email-validator>=2.3.0,pytest>=9.0.0,pytest-cov>=7.0.0,ruff>=0.14.0,ty>=0.0.31,pre-commit>=4.0.0,mkdocs>=1.6.1,mkdocs-material>=9.7.0,pymdown-extensions>=10.20. Brings in upstream security/bug fixes pip-auditCI: CVE-2026-3219 (pip 26.0.1) added to ignore list — Vulnerability inpipitself with no fix version published yet; documented in the workflow alongside the other ignored CVEs
Added¶
- Config validator: CrewAI + Logfire combination rejected — Raises
ValueErrorat config time; documents an upstream OpenTelemetry/logfire ≥ 4.30 conflict with CrewAI - Conditional pydantic pin for CrewAI — Generated
pyproject.tomlpinspydantic[email]>=2.11.0,<2.12when CrewAI is selected (CrewAI is incompatible with pydantic 2.12) - Stricter
tyrules in generatedpyproject.toml—unknown-argument,invalid-await,invalid-context-manager,missing-argument,not-iterable,invalid-return-type,invalid-type-formpromoted towarn - Integration test matrix —
TestGeneratedTemplateMatrixnow exercises 14 framework/database/RAG/channel combinations (project names prefixed withmatrix_to avoid package-name collisions). Suite: 405 passed, 3 skipped services/health.py— Newbuild_health_response(status, checks, details)helper extracted fromhealth.pyroute;health.pynow imports and calls it instead of defining it inlineservices/agent.py— NewAgentConnectionManagerclass extracted from all 5 AI framework blocks inagent.py. Single canonical implementation shared viafrom app.services.agent import AgentConnectionManagerUserService.get_or_create_oauth_user(provider, provider_id, email, full_name)— Encapsulates the find-by-oauth-id → find-by-email → link-or-create orchestration that was previously duplicated inline in all threegoogle_callbackroute handlersSyncSourceService._to_read(source)/list_connectors()—_to_readconverts aSyncSourceORM model toSyncSourceRead(includingjson.loadsfor SQLite config);list_connectors()is a@staticmethodthat iteratesCONNECTOR_REGISTRYand returnsConnectorListwithout requiring a DB sessionBaseVectorStore.get_document_list(collection_name)— Concrete (non-abstract) method on the base class; callsget_documents()and maps toRAGDocumentList. All vector store implementations (Milvus, Qdrant, ChromaDB, pgvector) inherit it automatically
[0.2.6] - 2026-04-18¶
Added¶
- Message rating feature — Users can rate AI assistant messages with thumbs up/down and optional feedback comments. Toggle behavior: clicking same button removes rating, clicking opposite button changes it. Only assistant messages are rateable
- Backend:
MessageRatingmodel (PostgreSQL/SQLite/MongoDB, SQLAlchemy/SQLModel), repository + service + schema layers,POST /conversations/{id}/messages/{messageId}/rateendpoint. Ratings persisted tomessage_ratingstable with unique constraint per user/message andCHECKconstraint on rating values (1/-1). Optional comment field (up to 2000 chars). Supports all 3 database variants - Admin API:
GET /admin/ratings(paginated list with filters),GET /admin/ratings/summary(aggregate stats),GET /admin/ratings/export(CSV/JSON download).GET /admin/conversations(paginated listing). All admin routes require admin role - WebSocket integration: Ratings data (user's rating, like/dislike counts) included in streaming message events and conversation history loading
- Frontend:
RatingButtonscomponent with like/dislike icons, comment dialog on dislike, optimistic count updates. Integrated intomessage-item.tsxfor assistant messages. Admin pages for ratings management and conversations listing - Frontend proxy routes:
POST/DELETE /api/conversations/{id}/messages/{messageId}/rateproxies,GET /api/v1/admin/ratings,/summary,/exportroutes,lib/admin-auth.tsutility for admin API calls - Documentation:
docs/howto/use-ratings.mduser guide, updateddocs/architecture.mdanddocs/permissions.md - Tests: 660+ lines of tests covering config validation, model generation, repository/service/route layers, all database variants
Security¶
- Removed JWT from WebSocket URL query string — WS auth now uses
Sec-WebSocket-Protocol(access_token.<JWT>) instead of?token=..., so tokens no longer leak into access logs orRefererheaders. Backend echoes the chosen application subprotocol back onaccept() - Removed
/api/auth/tokenhttpOnly downgrade endpoint —access_tokenis now returned in the body of/auth/login,/auth/me, and/auth/refreshproxy responses and kept in memory only (never persisted) - CSV export injection hardening — Admin ratings CSV export now prefixes cells starting with
= + - @(or tab/CR) with a single quote, preventing formula execution when opened in Excel/Sheets - Rating comments stored raw — Dropped
html.escapefrom comment sanitization; comments are rendered via React (auto-escaped) and CSV-escaped separately, so the DB stores original text
Changed¶
- Streaming admin ratings CSV export —
/admin/ratings/export?export_format=csvnow streams row-by-row via an async/sync generator instead of buffering the whole dataset in memory
[0.2.5] - 2026-04-12¶
Added¶
Conversation Sharing + Admin Conversation Browser¶
- Conversation sharing — Share conversations with other users (direct share by user ID) or generate public share links (UUID4 token). Permission levels:
view(read-only) andedit(can add messages). Owner can share, list shares, and revoke access. Recipients can also leave shared conversations ConversationSharemodel — New DB model across all 5 variants (PG+SQLModel, PG+SQLAlchemy, SQLite+SQLModel, SQLite+SQLAlchemy, MongoDB). Fields: conversation_id, shared_by, shared_with, share_token, permission. Unique constraint on (conversation_id, shared_with)- Share endpoints —
POST /conversations/{id}/shares(share or generate link),GET /conversations/{id}/shares(list shares, owner only),DELETE /conversations/{id}/shares/{share_id}(revoke),GET /conversations/shared-with-me(list shared with current user),GET /conversations/shared/{token}(public access, no auth) - Admin conversation browser — Admin-only endpoints:
GET /admin/conversations(paginated, searchable by title, filterable by user_id, includes message_count and user_email),GET /admin/conversations/{id}(full conversation with messages),GET /admin/conversations/users(user list with conversation counts, searchable) - Share dialog component — Frontend dialog to share conversations: user search input, permission dropdown (view/edit), generate share link with copy button, list current shares with revoke
- Admin conversations page —
/admin/conversationspage with tabs (Conversations/Users), table views, search, click-to-preview (read-only), user → conversations drill-down - Public shared page —
/shared/[token]SSR page renders conversation transcript without sidebar or input. Clean read-only view using server-side fetch - Frontend hooks —
useConversationShares(share, fetch, revoke, shared-with-me) anduseAdminConversations(admin list, users, detail preview)
Slack Multi-Bot Channel Integration¶
- Slack adapter —
SlackAdapter(ChannelAdapter)supporting both Events API (production webhook) and Socket Mode (development polling). Thread-aware: Slack thread replies foldthread_tsintoplatform_chat_id({channel}:{thread_ts}) so each thread gets its ownChannelSessionandConversation - Events API webhook —
POST /slack/{bot_id}/eventsendpoint handles Slack URL verification challenge and event dispatch. Signature verified via HMAC-SHA256 (v0={timestamp}:{body}) with 5-minute replay-protection window. Fire-and-forget background dispatch meets Slack's 3s response requirement - Socket Mode (dev) — Supervised polling loop with
slack-sdk'sSocketModeClient. Bot-scoped tasks with 5s back-off restart on crash. Lifecycle managed in app lifespan alongside Telegram polling use_slackcookiecutter variable — Gates all Slack infrastructure. CLI interactive prompt for "Enable Slack integration" added alongside Telegram. Enables:slack-sdk>=3.35.0, Slack-specific config vars (SLACK_SIGNING_SECRET,SLACK_BOT_TOKEN,SLACK_APP_TOKEN),POST /slack/{bot_id}/eventsroute- Shared channel infrastructure expanded — All 14 shared files (
ChannelAdapterbase, models, repos, services, router, commands) gated fromuse_telegram→use_telegram or use_slackso both platforms share the same session/identity/bot management layer - Group chat concurrency control — Per-chat
asyncio.Lock(keyed on{bot_id}:{platform_chat_id}) inChannelMessageRouter.route(). Serializes concurrent messages from the same group/channel to prevent: duplicateChannelSessioncreation (DB constraint violation), interleaved agent invocations on the sameConversation, and rate-limit counter races. Affects both Telegram groups and Slack channels
Telegram Multi-Bot Channel Integration¶
- Full Telegram bot integration — Multi-bot support with polling and webhook delivery modes, encrypted token storage (Fernet), in-memory rate limiting (token-bucket per user per bot), and role-based access policies (open, whitelist, jwt_linked, group_only)
- Channel adapter architecture — Abstract
ChannelAdapterbase class with concreteTelegramAdapter(aiogram v3). Adapter registry pattern for future platform extensions (Discord, Slack, etc.) - Channel message router — 8-step processing pipeline: load bot, check access, handle commands (/start, /new, /help, /link, /unlink, /project), resolve identity, resolve session, rate-limit, invoke agent, send reply
- 3 new DB models —
ChannelBot(encrypted token, access policy, webhook config),ChannelIdentity(platform user ↔ app user linking with link codes),ChannelSession(bot+chat → conversation mapping) - Admin API routes — Full CRUD for bot management (
/channels/bots), activate/deactivate, webhook register/delete, session listing. All endpoints require admin role with properChannelBotCreate/ChannelBotUpdate/ChannelBotReadschemas - Webhook endpoint —
POST /telegram/{bot_id}/webhookwith signature verification, fire-and-forget async processing to stay within Telegram's 5s timeout - Supervised polling — Per-bot polling loop with 5s back-off restart on crash, managed via lifespan startup/shutdown
- CLI commands —
channel-list-bots,channel-add-bot,channel-webhook-register,channel-webhook-delete,channel-test-message AgentInvocationService— Framework-agnostic non-streaming agent invocation for all 6 AI frameworks, used by Telegram channel routeruse_telegramcookiecutter variable — Gates all Telegram code via Jinja2 conditionals. CLI interactive prompt added
PydanticDeep Framework (6th AI Framework)¶
- PydanticDeep integration — Deep agentic coding assistant built on pydantic-ai with filesystem tools (ls, read_file, write_file, edit_file, glob, grep), task management, subagent delegation, skills system, memory persistence, and context discovery
- Sandbox environment selection in CLI — New interactive prompt when selecting DeepAgents or PydanticDeep:
- PydanticDeep: Docker sandbox (default), Daytona workspace, State (in-memory)
- DeepAgents: Docker sandbox (default), State (in-memory)
sandbox_backendcookiecutter variable — ConfiguresPYDANTIC_DEEP_BACKEND_TYPE/DEEPAGENTS_BACKEND_TYPEin generated Settings- File upload to sandbox workspace — When users attach files in chat, files are written to the Docker/Daytona sandbox via
docker cp(or backend API) so the agent can access them withread_file. File paths are automatically included in the user message. Falls back to inline content for StateBackend - Project-scoped WebSocket endpoint —
ws/projects/{project_id}/chats/{conversation_id}for shared Docker containers per project
PydanticAI Capabilities¶
- WebSearch and WebFetch as default capabilities — All PydanticAI agents now include
WebSearch()andWebFetch()capabilities. Provider-adaptive: uses builtin when the model supports it natively, falls back to DuckDuckGo (search) and markdownify (fetch) - pydantic-ai bumped to >=1.80.0 with
duckduckgoandweb-fetchextras for local fallback support
Changed¶
- Removed LocalBackend from PydanticDeep — Server-side filesystem backends are not appropriate for web apps. Only Docker/Daytona sandbox and StateBackend are supported
- Removed
PYDANTIC_DEEP_WORKSPACE_DIRsetting — No longer needed without LocalBackend
Fixed¶
- pgvector
vectorstore.pyf-string SyntaxError —metadata JSONB DEFAULT '{}'::jsonbwas rendered inside a Python f-string, causingSyntaxError: f-string: empty expression not allowedin generated projects using pgvector. Escaped the braces so the f-string renders{}literally. (#65)
Telegram Channel Code Review Fixes¶
channels/router.py— Maderoute()alwaysasync def(was sync for SQLite, causingasyncio.get_event_loop().run_until_complete()crash). Removed broken_handle_command_sync,_resolve_identity_sync,_resolve_session_syncmethods. Added SQLite branches to all async methodschannels/router.py— Fixed/linkcommand: replaced non-existentchannel_link_repo.redeem_code()withchannel_identity_repo.get_by_link_code(). Code is invalidated after usechannels/router.py— Fixedbot.encrypted_token→bot.token_encryptedin_send_reply(). Fixedbot.system_prompt→bot.system_prompt_overrideandbot.model_override→bot.ai_model_overridechannels/router.py— Fixed MongoDB import paths:from app.db.models.channel import→from app.db.models.channel_identity import/from app.db.models.channel_session importchannels/router.py— Added_parse_policy()helper to normalizeaccess_policyfrom JSON string (SQLite) or dict (PostgreSQL/MongoDB)channels/telegram.py— Removed module-level singleton that conflicted with lifespan-managed adapter inmain.py. Fixed SQLite_handle_updatetoawait router.route()api/routes/v1/channels.py— Fixed all service method names (service.list_bots()→service.list(), etc.). Replaceddata: Anywith properChannelBotCreate/ChannelBotUpdateschemas. Addedresponse_modelto all endpoints. Made SQLite webhook routesasync(was usingasyncio.run()inside running loop)api/routes/v1/telegram_webhook.py— Fixed SQLite branch toawait router.route()(route is now always async)services/channel_bot.py— Generatewebhook_secretviasecrets.token_urlsafe(32)whenwebhook_mode=True(was alwaysNone). Addedlist_sessions()method to all 3 backendsrepositories/channel_session.py— Addedlist_by_bot()andcount_by_bot()functions to all 3 backendscommands/channel.py— Fixedbot.encrypted_token→bot.token_encrypted. Fixedchannel_bot_repo.list_all(platform=...)(no such parameter) → conditionalget_by_platform(). Fixedencrypted_token=→token_encrypted=in create
Tooling¶
- CI: MongoDB job — Added missing
ty checkstep (was present in minimal and PostgreSQL jobs but absent from MongoDB) - Template pre-commit — Bumped ruff-pre-commit from
v0.8.0tov0.15.0(consistent withpyproject.toml >=0.15.0)
Dependencies¶
- pydantic-ai
>=1.77.0→>=1.80.0(all providers + pydantic-deep) - pydantic-ai extras: Added
duckduckgoandweb-fetchextras for WebSearch/WebFetch local fallback - aiogram
>=3.17,<4.0(new — Telegram adapter) - slack-sdk
>=3.35.0(new — Slack Web API, Socket Mode, Events API) - cryptography
>=44.0.0(new — Fernet token encryption, gated underuse_telegram or use_slack)
[0.2.4] - 2026-04-09¶
Security¶
- SSRF protection for webhook URLs (CWE-918) — Added
validate_webhook_url()inapp/core/sanitize.pythat blocks private/reserved/loopback/link-local/multicast/CGNAT IPs, validates DNS resolution against internal networks, rejects non-http(s) schemes and URLs with credentials. Validation enforced at webhook create, update, and delivery time across all three database variants (PostgreSQL, SQLite, MongoDB). IncludesSSRFBlockedErrorexception with proper 422 responses and 39 unit tests. (PR #62)
[0.2.3] - 2026-04-05¶
Added¶
.claude/directory in generated projects — Full Claude Code project structure so generated projects work as AI-native codebases out of the boxsettings.json— Auto-allow permissions for safe operations (Read, Glob, Grep, git, pytest, ruff, ty, alembic)rules/architecture.md— Layered architecture patterns (Routes → Services → Repositories), DI withAnnotatedaliases,db.flush()convention, domain exceptionsrules/code-style.md— Type hints (str | None), naming conventions table, import ordering (stdlib → third-party → local), ruff configrules/schemas-models.md— Pydantic v2*Create/*Update/*Read/*Listpattern,BaseSchemawithConfigDict, SQLAlchemyMapped[]columns,TimestampMixinrules/exceptions-security.md— Domain exception hierarchy (AppException→NotFoundError, etc.), JWT/bcrypt patterns,RoleChecker, API key verificationrules/api-conventions.md— REST design, pagination (Query(ge=0, le=100)), auth deps (CurrentUser/CurrentAdmin/ValidAPIKey), response format, file uploadrules/testing.md— Async test patterns,httpx.AsyncClient, fixtures, exception testing withpytest.raisesrules/frontend.md— Next.js 15 App Router, Server Components, Tailwind conventions (auto-removed when frontend disabled)commands/review.md—/project:reviewslash command: checks changes against architecture, types, security, and runs lintingcommands/add-endpoint.md—/project:add-endpointslash command: scaffolds full CRUD (schema → model → repo → service → deps → route → migration → test)commands/fix-issue.md—/project:fix-issueslash command: traces through layers, fixes, tests, lints- Enhanced
CLAUDE.md— Rewritten with precise patterns from the actual codebase: architecture layers, DI pattern, schema conventions, exception table, response format examples, key conventions
Changed¶
- Replaced mypy with ty — Astral's Rust-based type checker (from the makers of ruff/uv). Updated across:
pyproject.toml, Makefile, CI (GitHub Actions + GitLab CI), pre-commit config,.gitignore - Dependency version bumps — All generated project dependencies updated to latest stable versions:
- Core: FastAPI 0.135.3, uvicorn 0.43.0, Pydantic 2.12.0, pydantic-settings 2.13.0
- Database: SQLAlchemy 2.0.40, asyncpg 0.31.0, alembic 1.18.0, sqlmodel 0.0.38, motor 3.7.0, beanie 1.29.0
- AI Frameworks: pydantic-ai 1.77.0, langchain 1.2.0, langchain-openai 1.1.0, langgraph 0.4.0, langgraph-checkpoint 4.0.0, crewai 1.13.0
- Vector Stores: pymilvus 2.6.0, qdrant-client 1.14.0, chromadb 1.5.0
- Infra: redis 7.3.0, celery 5.6.0, sentry-sdk 2.53.0, logfire 4.30.0, sqladmin 0.24.0, boto3 1.42.0
- Dev: pytest 9.0.0, ruff 0.15.0, ty 0.0.29
[0.2.2] - 2026-03-20¶
Changed — CLI Simplification (Breaking)¶
The interactive wizard and CLI have been significantly simplified. Many options that were previously user-configurable are now always enabled or have sensible defaults. This reduces decision fatigue and eliminates invalid configuration combinations.
- AI Agent always enabled — Removed
enable_ai_agentoption. AI agent with WebSocket streaming is always included. Stripped{%- if cookiecutter.enable_ai_agent %}conditionals from 53 template files. - Auth always JWT + API Key — Removed
AuthTypeenum and--authCLI option. JWT (user management, login, roles) + API Key (utility for programmatic access) are always included. StrippedWebSocketAuthType— WebSocket always uses JWT. - Database always required — Removed
DatabaseType.NONEand--database none. JWT needs user storage. Minimal preset now uses SQLite. - Conversation persistence always on — Removed
enable_conversation_persistenceoption. Chat history always saved to database. Stripped 139 template conditionals. - i18n always enabled — Removed
enable_i18noption.next-intlalways included with[locale]routing. Stripped 50 template conditionals. - Example CRUD removed — Removed
include_example_crudoption. Item model/routes/tests no longer generated. Post-gen hook always cleans up CRUD files. - Session management defaults to enabled — Changed default from
FalsetoTrue. - Admin panel simplified — Removed
AdminEnvironmentTypeenum and auth config prompts. Admin panel always usesdev_stagingenvironment restriction and always requires auth. Checkbox label clarified: "SQL Admin Panel (SQLAdmin) — web UI for browsing/editing database tables". - Background tasks default Celery — Changed default from
NonetoCelery. Celery is first option in wizard (was last).Noneoption kept for projects without Redis.
Added¶
CLI¶
--s3-ragflag — Enable S3/MinIO document ingestion from CLI (previously only available in interactive mode)- S3 ingestion prompt — Interactive wizard now asks "Enable S3/MinIO document ingestion?"
- Image description prompt — Interactive wizard now asks "Enable image description in documents?" for RAG
- Reranker type selection — Replaced boolean
--rerankerwith properRerankerTypeenum. User's choice (Cohere vs Cross-Encoder) is now preserved instead of being auto-determined by LLM provider - PDF Parser "All" option — New option installs all 3 parsers (PyMuPDF, LiteParse, LlamaParse). Runtime selection via
PDF_PARSERandCHAT_PDF_PARSERenv vars.PdfParserFactorycreates parser on demand - RAG without Celery — RAG now works with
BackgroundTasks(no Celery/Taskiq/ARQ required). Ingestion and sync run in-process via FastAPIBackgroundTasks. Removed validation that blocked RAG without a task queue - Retry, sync logs, cancel endpoints always available — Previously gated behind Celery/Taskiq/ARQ, now work with any background task backend
Backend¶
CHAT_PDF_PARSERenv var — Separate parser config for chat file attachments (independent from RAG ingestion). Defaults topymupdffor speedPdfParserFactory— Factory class for runtime PDF parser selection when "All" parsers installed- Conversation IDOR protection —
get_conversation()now validatesuser_idownership. Users can only access their own conversations - OAuth null password guard —
authenticate()checksuser.hashed_password is not Nonebeforeverify_password(). Prevents crash for OAuth-only users - ChatFile cascade delete — Added
ondelete="CASCADE"touser_idandmessage_idforeign keys in all 4 DB variants - SQLite ToolCall args deserialization — Added
field_validatoronToolCallBase.argsthat deserializes JSON strings for SQLite compatibility - Embedding dimension validation — Runtime check in
EmbeddingServicethat embedding output matches configureddim. RaisesValueErroron mismatch - Collection name validation — Regex check
^[a-zA-Z][a-zA-Z0-9_]{0,63}$on collection creation. Prevents SQL injection in pgvector and invalid names - Vector store connection cleanup — Qdrant
client.close()and PgVectorengine.dispose()in lifespan shutdown
RAG¶
- ChromaDB async compliance — All ChromaDB operations wrapped in
asyncio.to_thread()to avoid blocking the event loop - ChromaDB
_ensure_collection()— Added missing method.POST /collections/{name}now works with ChromaDB - ChromaDB filter support —
search()now parsesparent_doc_idfilter and passes as ChromaDBwhereclause - ChromaDB consistent metadata — Now uses
_build_chunk_metadata()(same as Milvus/Qdrant/pgvector) - Qdrant filter support —
search()now parses filter string and passes asquery_filterwithFieldCondition - RRF fusion key fix — Changed merge key from
content[:100](collision-prone) toparent_doc_id:chunk_num
Frontend¶
- Refresh token rotation —
/api/auth/refreshnow updatesrefresh_tokencookie when backend returns a new one - WebSocket connection guard — Prevents orphaned WebSocket instances by checking
CONNECTINGstate - Scroll pagination guard — Conversation sidebar scroll handler checks
isLoading+ fetch mutex prevents concurrent requests - Message deduplication — Chat messages cleared before loading conversation history, preventing duplicates on switch
Fixed¶
- Port validator — Returns descriptive error messages ("Port must be between 1024 and 65535") instead of generic
False - Reverse proxy default —
ReverseProxyType.NONEwhen Docker disabled (wasTRAEFIK_INCLUDED) - OpenRouter validation — Consolidated 4 separate checks into single message: "OpenRouter is only supported with PydanticAI, not {framework}"
- RAG prompt cancellation — All questionary calls in
prompt_rag_config()now wrapped with_check_cancelled(). Ctrl+C during RAG config shows "Cancelled." instead of crashing - Stale docstring — Removed bogus
Args: llm_providerfromprompt_rag_config()(function has no parameters) - Hardcoded
lang="en"— Root layout now uses locale from i18n config - Milvus version pinned — Dev compose files use
v2.5.10(waslatest), matching production - Frontend CI — Added
bun run lintandbun run type-checksteps to CI pipeline - Tailwind CSS — Updated from
^4.0.0-beta.8to^4.0.0(stable) - SQLite WebSocket auth —
contextmanager(get_db_session)()pattern verified working with mypy TYPE_CHECKINGimport for ChatFile — Added to SQLAlchemy PG and SQLite conversation model variants- Duplicate import — Removed duplicate
import Image from "next/image"inmessage-item.tsx - RAG search result off-by-one — Fixed array indexing in expanded view, uses
.find()by index value - Login loading state —
setLoading(false)now infinallyblock (was only incatch) - Refresh cookie clearing — Cookie options (
httpOnly,secure,sameSite) now consistent with logout route - Tool call status fallback —
statusConfig[status]falls back topendingfor unknown statuses
Added¶
Frontend — Landing Page¶
- Floating navbar with animated beam border — Pill-shaped navbar with rotating conic-gradient border in brand color, glass morphism background, adaptive dark/light mode
- Tech stack marquee — Infinite scrolling carousel with all project technologies (30+ items), edge fade mask, 60s animation loop
- Grid background — 64px grid pattern on hero section with radial gradient mask fade
- Glass cards — Frosted glass feature cards with backdrop-blur, hover lift animation, dark/light mode variants
- Brand color system — Global
--color-brandCSS variable (oklch), configurable hue presets (blue, green, red, violet, orange). Changes one value to retheme entire app - Footer — Two-column footer with product links, resources, API docs link, copyright
Frontend — Auth¶
- Split layout login/register — Left panel: dark bg with grid, gradient glow, heading, feature pills (AI Chat, KB, Auth, Real-time), quote. Right panel: form with contrasting background. Mobile: form only
- Auth guard — Client-side
AuthGuardcomponent wraps dashboard layout, redirects unauthenticated users to/loginwith loading spinner
Frontend — Dashboard¶
- Personalized greeting — "Good morning/afternoon/evening, {name}" based on time of day
- Stats row — Compact 4-column cards (API status, Conversations, Knowledge Base, AI Agent)
- Recent conversations — Last 5 chats with relative timestamps, skeleton loading
- Collections overview — Clickable RAG collection list with vector counts and status badges
- Quick actions grid — 2x2 icon grid (New Chat, Upload Docs, API Docs, Profile)
- Account card — Avatar with initials, email, role, registration date
- Environment card — Status, version, framework, LLM, vector store info
Frontend — Chat¶
- "Thinking..." indicator — Animated bounce dots before first content arrives from LLM
- Copy buttons — Appear on hover under both user and assistant messages (moved from inside bubble)
- Message timestamps — HH:MM format under each message, hidden during streaming
- File upload system — Upload images, text, PDF, DOCX via backend API. Thumbnail preview for images, badge for files. Files parsed on backend (PyMuPDF for PDF, python-docx for DOCX)
- Image support (LLM Vision) — Images sent as
BinaryContentto PydanticAI agent for vision analysis. Stored inmedia/directory, linked to messages viaChatFilemodel - Microphone (Speech-to-Text) — Web Speech API voice input button, always visible, toast fallback for unsupported browsers
- Message queue — Input not disabled during processing. Messages queued on frontend, auto-sent when bot finishes responding
- Model selector — Dropdown in chat status bar (Claude Sonnet 4, Claude 3.5 Sonnet, GPT-4o, GPT-4o Mini, Gemini 2.5 Flash). Backend
get_agent(model_name=...)accepts override - Tool call persistence — Tool calls (name, args, result, status) saved to database during WebSocket streaming. Visible when loading conversation history
Frontend — Knowledge Base (RAG Dashboard)¶
- Sidebar layout — Collections in left sidebar (collapsible), documents/search in main area
- Create collection — Inline input in sidebar with "+" button
- Upload & ingest — File upload → backend ingestion (parse, chunk, embed, store). Supports PDF, DOCX, TXT, MD. Max 50MB
- Document tracking —
RAGDocumentmodel in database: status (processing/done/error), error message, timestamps, storage path - Document list — Per-collection with filename, type badge, size, date, status icon (spinner/check/error)
- View original — Eye icon opens original uploaded file (stored in local storage / S3)
- Delete document — AlertDialog confirmation, removes from vector store + file storage + SQL
- Search — Full-text vector search with score badges, source document linking ("View source")
Frontend — UI Components (shadcn/ui + Radix)¶
- Dialog —
@radix-ui/react-dialogbased modal with overlay, close button, fade+zoom animations - AlertDialog —
@radix-ui/react-alert-dialogfor destructive action confirmations (delete collection, delete document) - Avatar —
@radix-ui/react-avatarwith image support and initial fallback - Skeleton — Pulse animation placeholder for loading states
- Separator —
@radix-ui/react-separatorfor visual dividers - Tooltip —
@radix-ui/react-tooltipwith TooltipProvider - Button
asChildfix —@radix-ui/react-slotenables properasChildprop on Button component
Frontend — Navigation & Layout¶
- Nav links in header — Dashboard, Chat, Knowledge Base, Profile tabs with icons and active state (moved from sidebar)
- Sidebar → mobile only — Desktop sidebar removed, kept as Sheet drawer for mobile
- Language switcher — Segmented control buttons (EN | PL) with
router.pushlocale switching - Softer dark theme — Zinc-inspired tones (14.5% lightness, subtle blue-purple hue 285) replacing pure black (12%)
BACKEND_URLconstant — API docs links point to backend (http://localhost:8000/docs), not frontend- Page transition animations — Fade-in + 6px slide-up animation (250ms ease-out) on dashboard page navigation via
PageTransitioncomponent withkey={pathname}re-mount - Breadcrumbs — Auto-generated breadcrumb navigation on Profile and Settings pages with
aria-label="Breadcrumb", chevron separators, clickable parent links
Frontend — Error Handling¶
- 404 page —
not-found.tsxwith "Page not found" message, "Go home" and "Dashboard" buttons, brand-colored styling - 500 error boundary —
global-error.tsxwith inline styles (no CSS dependency), "Try again" reset button, error digest ID display - Page error boundary —
[locale]/error.tsxcatches errors within layout, preserves header/sidebar, "Try again" + "Go home" buttons
Frontend — Accessibility¶
aria-current="page"— Active nav items in header marked for screen readersaria-live="polite"— Loading/status changes announced: auth guard, chat "Thinking..." indicator, RAG upload progress, RAG document status iconsaria-hidden="true"— Decorative bounce dots and spinner icons hidden from screen readersrole="status"— RAGStatusIconcomponent with descriptivearia-label(Completed/Failed/Processing)
Backend — File System¶
ChatFilemodel — Tracks files uploaded in chat (user_id, message_id, filename, mime_type, storage_path, file_type, parsed_content)LocalFileStorageservice — Save/load/delete files inmedia/{user_id}/directory. ExtensibleBaseFileStorageABC for S3/MinIOPOST /files/upload— Multipart upload with MIME validation, 10MB limit, auto-parsing (text/PDF/DOCX)GET /files/{id}— Download with owner-only access check- File linking — Files linked to messages via
message_idFK, loaded with conversation history
Backend — RAG Improvements¶
- Async Celery ingestion —
POST /rag/collections/{name}/ingestreturns202 Acceptedand dispatchesingest_document_taskto Celery worker. Falls back to synchronous ingestion when Celery is not enabled - WebSocket status updates —
WS /rag/ws/statusendpoint subscribes to Redis pub/sub channelrag_status, forwards real-time ingestion status (processing → done/error) to frontend - RAG is global — Removed
user_idfromRAGDocumentmodel. All documents, collections, and vectors are shared across users (no per-user isolation) - CLI DB tracking —
rag-ingestCLI command now createsRAGDocumentrecords in SQL for each file. Documents ingested via CLI appear in the Knowledge Base dashboard with status tracking - Retry endpoint —
POST /rag/documents/{id}/retryresets failed document status toprocessingfor re-ingestion RAGDocumentmodel — Tracks ingestion status (processing/done/error), error_message, vector_document_id, storage_path, timestampsGET /rag/documents— List tracked documents with collection filterGET /rag/documents/{id}/download— Download original ingested fileDELETE /rag/documents/{id}— Removes from vector store + file storage + SQL (3-way cleanup)- Configurable upload size —
MAX_UPLOAD_SIZE_MBsetting (default 50MB) used in file upload, RAG ingest, and health endpoint. Frontend reads fromNEXT_PUBLIC_MAX_UPLOAD_SIZE_MBenv var - Batch upload — Frontend RAG page supports multi-file upload with progress bar (X/Y files, filename indicator)
- Filename fix in ingestion —
document.metadata.filenameset fromsource_pathinstead of temp file name EmbeddingsConfigmodel validator — Auto-derives vector dimensions from model name viaEMBEDDING_DIMENSIONSlookup tableEMBEDDING_MODELenv var wired — Settings → RAGSettings → EmbeddingsConfig flow complete
Backend — Agent Improvements¶
- Tool call persistence — WebSocket handler collects tool calls during streaming, saves to DB after assistant message
- Conversation title auto-set — Backend updates conversation title from first user message when title is empty
- Model selection — WebSocket handler accepts
modelfield, passes toget_agent(model_name=...) - File handling in WS — Parses
file_idsfrom WebSocket message, loads files, images →BinaryContentfor LLM vision, text/PDF/DOCX → parsed content appended to prompt
Backend — Security & Compliance¶
- PII redaction in logs —
PiiRedactionFilterlogging filter scrubs emails, JWT tokens, API keys (OpenAI/Anthropic), Bearer tokens, and password-like values from all log output. Prevents PII leaks to log aggregators (Datadog, CloudWatch, Logfire). Activated viasetup_logging()at app startup - Dependency vulnerability scanning — CI pipeline includes
securityjob withpip-auditfor supply chain risk detection - Docker image scanning — Trivy vulnerability scanner (
aquasecurity/trivy-action@0.28.0) runs after Docker build in CI, reports CRITICAL and HIGH severity issues
Backend — Database & Infrastructure¶
- Missing indexes added —
users.oauth_provider,users.oauth_id,sessions.user_id,webhooks.user_id,webhook_deliveries.webhook_id,webhook_deliveries.created_at(all SQL variants) MEDIA_DIRconfig — New setting for file storage directoryimport sqlmodelin migrations — Added toscript.py.makotemplate for SQLModel projects- Graceful RAG startup — Embedding, reranker, vector store warmup wrapped in try/except, app doesn't crash on failure
Changed¶
- Auth layout theme-aware — Left hero panel now adapts to light/dark mode (
bg-zinc-100 dark:bg-zinc-950) instead of hardcodedbg-zinc-950 next/imagemigration — Chat file thumbnails and attached images usenext/imagewithunoptimizedfor lazy loading and layout stability- Consistent loading states — All
"..."placeholder text and"Loading..."messages replaced withSkeletoncomponents (dashboard stats, conversation sidebar, OAuth callback) - RAG sidebar responsive — Sidebar width
w-52 lg:w-64(narrower on tablets, full width on desktop) - Empty search state — RAG search shows in-UI "No results found" with icon instead of toast-only
- Profile inline update —
window.location.reload()replaced with ZustandsetUser()for instant state update without page reload - Real-time form validation — Register: email format on blur, password strength bar (Weak/Fair/Good/Strong), confirm password match indicator. Login: email format on blur
TimestampMixinusessa_column_kwargs— Fixed SQLModel shared Column object bug (Column 'created_at' already assigned to Table)MessageListschema — Changed fromMessageReadSimple(no tool_calls) toMessageRead(with tool_calls + files)list_messagesendpoint — Now passesinclude_tool_calls=Trueto eagerly load tool call relationships- RAG proxy routes — Removed
NEXT_PUBLIC_AUTH_ENABLEDgate, always forward auth cookie if present - Removed unused code —
pipelines/directory,repositories/base.py,worker/tasks/rag_ingestion.py(reindex task),test_pipelines.py,docs/howto/add-data-pipeline.md - Removed section dividers —
# =========comments removed fromservices/conversation.py,repositories/conversation.py,schemas/conversation.py itsdangerousdependency — Added when OAuth enabled (required by StarletteSessionMiddleware)loggeradded tomain.py—import logging+logger = logging.getLogger(__name__)for RAG startup error logging- Document ingestion is CLI-only - Removed upload API endpoints. Ingestion exclusively via CLI commands (later re-added as
POST /rag/collections/{name}/ingest) RetrievalServicerenamed -MilvusRetrievalService→RetrievalService(now backend-agnostic)- Docker validation relaxed - Docker only required for Milvus and Qdrant vector stores
.env.examplerestructured - RAG section moved fromuse_milvustoenable_ragguard- README.md rewritten - Updated for 5 frameworks, 4 providers, RAG section with vector store/embedding tables
- CLAUDE.md / AGENTS.md updated - Both project-level and template versions updated with RAG, Gemini, vector stores
Fixed¶
- Language switching —
usePathname()with next-intl returns path with locale prefix, originalsegments[1] = newLocale+router.pushlogic restored - Conversation "20531d ago" —
updated_atnull fallback tocreated_atin dashboard - RAG stats "..." — Set
{collections: [], totalVectors: 0}on error instead of null callable | NoneTypeError — Changed toCallable | Nonewith proper import iningestion.py- Tool call card TS error —
Type 'unknown' not assignable to ReactNodefixed with ternary - Empty args crash —
json.loads("")in tool call persistence fixed with.strip()check user_promptserialization —BinaryContentnot JSON serializable, now sends text-only prompt in WS event- File linking FK violation — ChatFile
message_idupdate moved to same DB session as message insert
RAG (Retrieval-Augmented Generation) — Pipeline¶
- RAG integration - Full RAG pipeline: document parsing → chunking → embedding → vector store → retrieval. Integrated with all 5 AI frameworks as
search_knowledge_basetool - 4 vector store backends - Milvus (Docker), Qdrant (Docker), ChromaDB (embedded), pgvector (PostgreSQL extension). Selected via
vector_storeconfig option - 4 embedding providers - OpenAI (
text-embedding-3-small), Voyage (voyage-3), Google Gemini (gemini-embedding-exp-03-07, multimodal), SentenceTransformers (all-MiniLM-L6-v2) - Document parsers - PyMuPDF (PDF text + tables + headers/footers + images + OCR), LlamaParse (130+ formats via cloud API, configurable tier), python-docx (DOCX), native (TXT/MD)
- Image description - Optional extraction of images from documents via PyMuPDF + LLM vision API description (OpenAI GPT-4o / Anthropic Claude / Gemini / OpenRouter). Opt-in via
enable_rag_image_description - Chunking strategies - 3 strategies:
recursive(default),markdown(split by headers),fixed(simple fixed-size). Configurable viaRAG_CHUNKING_STRATEGYenv var - Hybrid search - BM25 keyword search + vector similarity search with Reciprocal Rank Fusion (RRF). Enable via
RAG_HYBRID_SEARCH=true - Reranking - Cohere API or local CrossEncoder for improved search quality
- Citation/source tracking - Agent tool returns
[1] Source: filename, page X, chunk Yformat. Agent prompt instructs citation with[1],[2]references and source list - Document versioning -
source_path(local path /gdrive://id/s3://bucket/key) andcontent_hash(SHA256) in metadata. Automatic deduplication: re-ingest replaces old chunks. CLI:--replace/--no-replace - Multi-collection search -
RetrievalService.retrieve_multi()searches across multiple collections. API:collection_names: list[str]. Frontend: "All collections" option - Document sources - Local files (CLI
rag-ingest), Google Drive (service account, CLIrag-sync-gdrive), S3/MinIO (CLIrag-sync-s3). ExtensibleBaseDocumentSourceABC - Ingestion progress -
tqdmprogress bar in CLIrag-ingestwith per-file status and replaced count - RAG management page - Frontend
/ragpage: collection list with stats, search preview with results, metadata filters (filetype, min score), multi-collection support, delete collection - RAG API endpoints -
GET/POST/DELETE /rag/collections,GET /rag/collections/{name}/info,GET /rag/collections/{name}/documents,POST /rag/search,DELETE /rag/collections/{name}/documents/{id} - RAG CLI commands -
rag-collections,rag-ingest,rag-search,rag-drop,rag-stats,rag-sync-gdrive,rag-sync-s3
AI / LLM¶
- Google Gemini LLM provider - New
--llm-provider googleoption. PydanticAI:GoogleModel+GoogleProvider. LangChain/LangGraph/CrewAI/DeepAgents:ChatGoogleGenerativeAI. Dependencies:pydantic-ai-slim[google],langchain-google-genai - Gemini multimodal embeddings -
GeminiEmbeddingProviderwithembed_image()for native multimodal (text + images in same vector space). Model:gemini-embedding-exp-03-07(3072 dim)
Frontend¶
- Toast notification system -
sonnerlibrary with<Toaster />in providers. Toast feedback on: login, register, logout, profile save, RAG operations - Profile save wired - "Save Changes" button now calls
PATCH /users/me. Editable email field, loading state, toast feedback - Dashboard redesigned - Stats cards (API status, account, AI framework, RAG vector count), quick action links (Chat, Knowledge Base, Profile)
- Settings page - New
/settingspage with sections: Appearance (theme toggle), Application (project info, AI framework, vector store), Stack (technology badges), Security (auth type, rate limiting) - Metadata filtering UI - RAG search page: filetype dropdown, min score dropdown, "Clear filters" link
- Specialized tool call cards - DateTime tool: Calendar/Clock icons with formatted date/time. RAG search: horizontal card carousel with filename, page, score badges, expandable content. Toggle between formatted and raw JSON view
- Sidebar "Knowledge Base" link - Navigation item with Database icon, conditional on
enable_rag
DevOps¶
make quickstart- One command to install deps, start Docker services, run migrations, create admin user- Vercel deployment -
frontend/vercel.jsonconfig +make vercel-deploytarget with env var instructions - Qdrant Docker service - Added to
docker-compose.dev.ymlwith health check, volume, and backend env vars
PR #50 RAG Bug Fixes (28 issues found and fixed)¶
schedules.pyregression - Outer conditional broke Taskiq scheduling for all non-RAG projects. Fixed: restoreduse_taskiqguard, RAG schedule inside nested conditional- Duplicate Milvus settings -
core/config.pyhad Milvus settings twice. Fixed: removed duplicate, nested underuse_milvusinsideenable_rag - Env vars not wired to RAGSettings -
RAG_CHUNK_SIZE,RAG_DEFAULT_COLLECTIONignored at runtime. Fixed: wired throughSettings.ragcomputed property - Copy-paste etcd command in prod MinIO -
docker-compose.prod.ymlminio had etcd command. Fixed: removed - Frontend type mismatch -
RAGSearchResult.textvs backendcontent. Fixed: renamed tocontent, addedparent_doc_id - Milvus filter injection -
document_idinterpolated unsanitized. Fixed: strip"and\before interpolation - File upload security - No filename sanitization, no size limit. Fixed:
_safe_filename(),MAX_UPLOAD_SIZE=50MB, HTTP 413 - Hardcoded MinIO credentials in prod - Fixed: env var substitution
${MINIO_ROOT_USER}/${MINIO_ROOT_PASSWORD} milvusdb/milvus:latestin prod - Fixed: pinned tov2.5.10processor.parser.allowedAttributeError - Fixed: useDocumentExtensionsenum directly- Inconsistent default collection - sync wrapper
"default"vs async"documents". Fixed: both"documents" RerankServiceNameError when disabled - Fixed:from __future__ import annotationsprint()in reranker.py - Fixed: all 13print()→logger.info()console.login rag-api.ts - Fixed: removed debug statements- Legacy typing imports in schemas - Fixed:
List→list,Dict→dict,Optional→| None - Hardcoded
/tmp/rag_uploads- Fixed:tempfile.gettempdir() - Inconsistent frontend auth - Fixed: all RAG routes use
NEXT_PUBLIC_AUTH_ENABLEDpattern - Inline
import loggingin routes - Fixed: moved to module level - Unconditional PDF parser import - Fixed: conditional on
not use_llamaparse - Chat page not removed when i18n disabled - Fixed: remove both
[locale]/and direct paths stores/index.tsunconditional chat exports - Fixed: previously gated behindenable_ai_agent(now always enabled)types/index.tsunconditional chat export - Fixed: previously gated behindenable_ai_agent(now always enabled)chat-sidebar-store.tsnot cleaned up - Fixed: added to post-gen hookrag/config.pygated byuse_milvus- Fixed: changed toenable_rag- Worker tasks gated by
use_milvus- Fixed:rag_ingestion.py,celery_app.py,arq_app.pychanged toenable_rag - pgvector SQL injection - Fixed:
_validate_collection_name()regex +_table()helper in all methods - pgvector IVFFlat on empty table - Fixed: changed to HNSW index
- Duplicate
loggerin reranker.py - Fixed: removed duplicate, reordered imports
[0.2.1] - 2026-03-05¶
Added¶
- LangSmith observability integration - New
enable_langsmithoption for LangChain, LangGraph, and DeepAgents frameworks. AddsLANGCHAIN_TRACING_V2,LANGCHAIN_API_KEY,LANGCHAIN_PROJECT,LANGCHAIN_ENDPOINTsettings andlangsmithdependency when enabled. Includes interactive prompt,--langsmithCLI flag, and auto-enable inai-agentpreset. Previously LangSmith env vars were hardcoded to appear with LangChain — now requires explicit opt-in and works with all 3 LangChain-ecosystem frameworks. - CLI:
--conversation-persistenceflag - Enable conversation persistence from the command line (previously only available interactively) - CLI:
--websocket-authoption - Set WebSocket authentication method (none,jwt,api_key) from the command line
Changed¶
- CLI runs interactive wizard by default -
fastapi-fullstacknow launches the configurator directly without requiringnewsubcommand - CLI branding updated - Banner, descriptions, and prog name updated from "fastapi-gen / FastAPI Project Generator with Logfire" to "Full-Stack AI Agent Template Generator"
templatescommand expanded - Now lists all 5 AI frameworks, LangSmith, ORM options, WebSocket auth, conversation persistence, and port options
Fixed¶
CLI Fixes¶
--ai-frameworkmissing 3 frameworks - Addedlanggraph,crewai,deepagentsto choices (previously onlypydantic_aiandlangchain)
Backend Template Fixes¶
- Unconditional
import logfireinversioning.py- Replaced withloggingmodule to preventImportErrorwhen Logfire is disabled - Unconditional
import logfireinwebhook.py- Replacedlogfire.error()/logfire.info()withlogger.error()/logger.info()in all 3 database sections (PostgreSQL, SQLite, MongoDB) to preventImportErrorwhen Logfire is disabled but webhooks are enabled backend/.envLangSmith conditional wrong - Was gated byuse_langchaininstead ofenable_langsmith(only.env.examplewas updated,.envwas missed)backend/.envmissing sections - Added OAuth Google, ARQ, and Prometheus sections that were present in.env.examplebut absent from generated.env
Frontend Template Fixes¶
- Sidebar "Chat" link visible without AI agent - Chat navigation item now always visible (AI agent is always enabled)
- Frontend chat files not cleaned up - No longer applicable (AI agent is always enabled, chat files are always present)
- Chat component exports unconditional - Chat exports are now always included (AI agent is always enabled)
- Chat hook exports unconditional - Hook exports for
useWebSocket,useChat,useLocalChatare now always included (AI agent is always enabled) WS_URLandROUTES.CHATalways defined -constants.tsnow conditionally defines WebSocket URL and chat route only when AI agent is enabled
[0.2.0] - 2026-02-27¶
Changed¶
- Repository renamed from
full-stack-fastapi-nextjs-llm-templatetofull-stack-ai-agent-template— all internal links, badges, raw.githubusercontent URLs, mkdocs config, and template files updated. Old GitHub URLs redirect automatically. - Repository marked as GitHub Template — users can now click "Use this template" to create a new repo directly from GitHub
- README CTA section — replaced "Made with" footer with Vstorm consultancy call-to-action
[0.1.18] - 2026-02-01¶
Fixed¶
- Removed macOS
.DS_Storeartifacts and added.DS_Storeto.gitignore(contributed by @vladdoster in #42)
[0.1.17] - 2026-01-24¶
Added¶
- MkDocs Material documentation site with pink theme, Inter/JetBrains Mono fonts
- New documentation pages:
docs/index.md- Landing page with quick start and features overviewdocs/installation.md- Installation guide with uv/pip/pipx optionsdocs/getting-help.md- FAQ and support resourcesdocs/concepts/index.md- Architecture overview with Mermaid diagramsdocs/guides/quick-start.md- Step-by-step first project guidedocs/guides/configuration.md- All configuration options- GitHub Actions workflow for automatic docs deployment (
docs.yml) - Custom styling (
docs/stylesheets/extra.css) matching pydantic-deep theme - GitHub announcement bar in docs header
Changed¶
- README.md redesign:
- New centered header with "Full-Stack AI Agent Template" title
- Reorganized badges: AI frameworks (PydanticAI, LangChain, LangGraph, CrewAI, OpenAI, Anthropic, OpenRouter) moved to Features section
- Added infrastructure badges: FastAPI, Next.js 15, React 19, TypeScript, Tailwind v4, SQLAlchemy, PostgreSQL, MongoDB, Redis, Celery, Logfire, Sentry, Prometheus, Docker, Kubernetes, GitHub Actions, S3
- New highlights: "🤖 PydanticAI • 🦜 LangChain, LangGraph & DeepAgents • 👥 CrewAI • 🎯 Fully Type-Safe"
- Moved
CHANGELOG.mdfromdocs/to project root (symlinked in docs) - Added
docsoptional dependency group with mkdocs packages
[0.1.16] - 2026-01-20¶
Fixed¶
- Logfire Celery instrumentation prompt - Celery instrumentation option now only appears when Celery is selected as background task system (previously caused validation error when selecting the option with Taskiq/ARQ)
Changed¶
- Prompt order - Background tasks prompt now appears before Logfire prompt to enable dynamic feature filtering
Tests Added¶
- Test for Celery instrumentation option visibility based on background task selection
[0.1.15] - 2026-01-18¶
Added¶
DeepAgents Framework Support¶
- DeepAgents as fifth AI framework option alongside PydanticAI, LangChain, LangGraph, and CrewAI
- New
--ai-framework deepagentsCLI option for project creation - Interactive prompt includes "DeepAgents" choice
- Built-in tools for file operations, code execution, and task management:
ls,read_file,write_file,edit_file,glob,grepexecute(disabled by default for safety)write_todos,task(sub-agents)- StateBackend for in-memory file state management
- Skills support via
DEEPAGENTS_SKILLS_PATHSenvironment variable - New template files:
app/agents/deepagents_assistant.py- DeepAgentsAssistant class with run() and stream()- WebSocket route implementation with interrupt handling
- New cookiecutter variable:
use_deepagents - Dependencies:
deepagents>=0.1.0
Human-in-the-Loop (HITL) Support¶
- Tool approval workflow for DeepAgents allowing users to approve, edit, or reject tool calls
- Configurable via
DEEPAGENTS_INTERRUPT_TOOLSenvironment variable: - Comma-separated tool names (e.g.,
write_file,edit_file,execute) - Or
allto require approval for all tools - Frontend tool approval dialog (
tool-approval-dialog.tsx): - Shows pending tool calls with JSON args in editable textarea
- Cancel/Save buttons for editing args
- Submit button to send decisions
- Auto-detects changes and sends appropriate decision (approve/edit/reject)
- WebSocket protocol for interrupt handling:
- Backend sends
tool_approval_requiredevent with action requests - Frontend sends
resumemessage with user decisions - Thread ID management for state persistence across interrupts
- New types in
chat.ts:ActionRequest,ReviewConfig,PendingApproval,Decision - Updated hooks (
use-chat.ts,use-local-chat.ts) with: pendingApprovalstatesendResumeDecisions()function- Environment variables:
DEEPAGENTS_INTERRUPT_TOOLS- tools requiring approvalDEEPAGENTS_ALLOWED_DECISIONS- allowed decision types (approve, edit, reject)
Fixed¶
- OAuth requires JWT authentication - Added validation that OAuth providers (Google) require JWT auth to be enabled, preventing invalid configuration combinations
Changed¶
AIFrameworkTypeenum extended withDEEPAGENTSvalue- AI framework prompt now shows five options: PydanticAI, LangChain, LangGraph, CrewAI, DeepAgents
- LLM provider validation - OpenRouter not supported with DeepAgents (uses LangChain providers directly)
VARIABLES.mdupdated withuse_deepagentsdocumentation- Template
CLAUDE.mdincludes DeepAgents in stack section CONTRIBUTING.mdupdated with correct repository URL
Tests Added¶
- Tests for
DEEPAGENTSenum value - Tests for DeepAgents + OpenRouter validation (combination is rejected)
- Tests for
use_deepagentscomputed field - Tests for cookiecutter context generation with DeepAgents
[0.1.14] - 2026-01-16¶
Fixed¶
- Dynamic version reading -
fastapi-fullstack --versionnow correctly displays the version frompyproject.tomlusingimportlib.metadatainstead of hardcoded value
Security¶
- CVE-2026-22701 - Updated
filelockto 3.20.3 - CVE-2026-21441 - Updated
urllib3to 2.6.3 - CVE-2026-22702 - Updated
virtualenvto 20.36.1
[0.1.13] - 2026-01-06¶
Added¶
Comprehensive Configuration Validation¶
- New validation rules to prevent invalid option combinations at config time:
- WebSocket JWT auth requires main JWT auth to be enabled
- WebSocket API key auth requires main API key auth to be enabled
- Admin panel authentication requires JWT auth (for User model)
- Conversation persistence requires AI agent to be enabled
- Admin panel requires SQLAlchemy ORM (SQLModel not fully supported by SQLAdmin)
- Session management requires JWT auth
- Webhooks require a database to store subscriptions and delivery history
- Background task queues (Celery/Taskiq/ARQ) require Redis as broker
- Logfire database instrumentation requires a database
- Logfire Redis instrumentation requires Redis
- Logfire Celery instrumentation requires Celery as background task system
Improved Post-Generation Instructions¶
- Clearer database setup instructions with warning message:
- README.md updated with prominent warning about required migration steps
- Commands displayed with aligned descriptions for better readability
Dynamic Integration Prompts¶
- Context-aware integration options in interactive wizard:
- Admin Panel option only shown when SQLAlchemy is selected (not SQLModel)
- Webhooks option only shown when a database is enabled
- WebSocket auth options filtered based on main auth type selected
- Clearer ORM selection labels: "SQLAlchemy — full control, supports admin panel" vs "SQLModel — less boilerplate, no admin panel support"
- Auto-enable Redis when caching is selected (with info message)
- Better descriptions for all integration options explaining dependencies
Template Improvements¶
- ARQ worker service added to
docker-compose.prod.yml - Prometheus labels added to backend service in
docker-compose.dev.yml - OAuth environment variable
NEXT_PUBLIC_API_URLadded to frontend.env.example - Frontend WS_URL now uses
backend_portcookiecutter variable instead of hardcoded 8000
Changed¶
Post-Generation Hook Improvements¶
- Stub file cleanup - removes files containing only docstrings with no actual code
- Auth file cleanup - removes auth/user files when JWT is disabled:
auth.py,users.pyroutesuser.pymodel, repository, service, schematoken.pyschema- Logfire cleanup - removes
logfire_setup.pywhen Logfire is disabled - Security cleanup - removes
security.pywhen no auth is configured at all - LangGraph/CrewAI cleanup - properly removes unused AI framework files
Fixed¶
Template Fixes¶
- LangChain assistant
stream()method - changed from sync generator to async generator usingastream()for proper async streaming - OAuth callback - made fully async, removed sync
asyncio.new_event_loop()hack - Deprecated
datetime.utcnow()- replaced withdatetime.now(UTC)across all services: cleanup.pycommandsession.pyrepository and serviceconversation.pyservicewebhook.pyservice- Session model - added missing
default_factory=datetime.utcnowforcreated_atandlast_used_atfields - Webhook model - moved
import jsonto module level instead of inside properties - Admin panel template condition - now correctly checks for SQLAlchemy ORM requirement
- Caching setup - only runs when both caching AND Redis are enabled
- Config imports - fixed conditional imports for Redis-only projects (no database)
Tests Added¶
- 290+ new test lines covering all new validation rules
- Tests for WebSocket auth requiring main auth
- Tests for admin panel requiring SQLAlchemy
- Tests for admin authentication requiring JWT
- Tests for conversation persistence requiring AI agent
- Tests for webhooks requiring database
- Tests for Logfire feature dependencies (database, Redis, Celery)
- Tests for background task queues requiring Redis
- Updated CLI tests to include
--redisflag with task queue options
[0.1.12] - 2026-01-02¶
Added¶
CrewAI Multi-Agent Framework Improvements¶
- Full type annotations for all CrewAI event handlers in
crewai_assistant.py - Comprehensive event queue listener with handlers for:
crew_started,crew_completed,crew_failedagent_started,agent_completedtask_started,task_completedtool_started,tool_finishedllm_started,llm_completed- Improved stream method with proper thread and queue handling:
- Natural completion path when receiving None sentinel
- Race condition handling for thread death scenarios
- Defensive code with
# pragma: no coverfor edge cases - 100% test coverage for CrewAI assistant module
Fixed¶
Backend Fixes¶
- Type annotations - All mypy errors fixed across the codebase:
- Added
Anytypes where needed inlogfire_setup.py - Fixed
Callabletypes incommands/__init__.py - Added proper types to versioning middleware
- Full type coverage for CrewAI event handlers
- WebSocket disconnect handling - Proper logging and cleanup when client disconnects during agent processing (lines 241-242 in
agent.py) - Health endpoint edge cases - Added
# pragma: no coverfor unreachable 503 response path (checks dict is always empty) - Abstract method coverage - Added
# pragma: no coverfor abstractrun()method inBasePipeline
Frontend Fixes¶
- Timeline connector lines for grouped messages now display correctly
- Message grouping visual indicators properly connect related messages
Tests Added¶
- 100% code coverage achieved (720 statements, 0 missing)
- Tests for all 11 CrewAI event handlers:
test_crew_started_handler,test_crew_completed_handler,test_crew_failed_handlertest_agent_started_handler,test_agent_completed_handlertest_task_started_handler,test_task_completed_handlertest_tool_started_handler,test_tool_finished_handlertest_llm_started_handler,test_llm_completed_handler- Tests for CrewAI stream method edge cases:
test_stream_complete_flow- natural completion pathtest_stream_empty_queue_break- queue empty handlingtest_stream_with_error- error event handling- Tests for WebSocket disconnect during processing:
test_websocket_disconnect_during_streamtest_websocket_disconnect_during_processing- Tests for health endpoint edge cases:
test_readiness_probe_503_unit- 503 response logic
[0.1.11] - 2026-01-02¶
Added¶
LangGraph ReAct Agent Support¶
- LangGraph as third AI framework option alongside PydanticAI and LangChain
- New
--ai-framework langgraphCLI option for project creation - Interactive prompt includes "LangGraph (ReAct agent)" choice
- ReAct (Reasoning + Acting) agent pattern with graph-based architecture:
- Agent node for LLM reasoning and tool decision
- Tools node for executing tool calls
- Conditional edges for tool execution loop
- Memory-based checkpointing for conversation continuity
- Full WebSocket streaming support using
astream()with dual modes: messagesmode for token-level LLM streamingupdatesmode for node state changes (tool calls/results)- Tool result correlation - proper
tool_call_idmatching between calls and results - New template files:
app/agents/langgraph_assistant.py- LangGraphAssistant class with run() and stream()- WebSocket route implementation in
app/api/routes/v1/agent.py - New cookiecutter variable:
use_langgraph - Dependencies for LangGraph projects:
langchain-core>=0.3.0langchain-openai>=0.3.0orlangchain-anthropic>=0.3.0langgraph>=0.2.0langgraph-checkpoint>=2.0.0
Changed¶
AIFrameworkTypeenum extended withLANGGRAPHvalue- AI framework prompt now shows three options: PydanticAI, LangChain, LangGraph
- LLM provider validation - OpenRouter not supported with LangGraph (same as LangChain)
VARIABLES.mdupdated withuse_langgraphdocumentation- Template
CLAUDE.mdincludes LangGraph in stack section
[0.1.10] - 2025-12-27¶
Added¶
Nginx Reverse Proxy Support¶
- Nginx as alternative to Traefik with two configuration modes:
nginx_included: Full Nginx setup in docker-compose.prod.ymlnginx_external: Nginx config template only, for external Nginx- Nginx configuration template (
nginx/nginx.conf) with: - Reverse proxy for backend API (api.DOMAIN)
- Reverse proxy for frontend (DOMAIN) - conditional
- Reverse proxy for Flower dashboard (flower.DOMAIN) - conditional
- WebSocket support for
/wsendpoint - Security headers (X-Frame-Options, X-Content-Type-Options, HSTS, etc.)
- HTTP to HTTPS redirect
- SSL/TLS configuration with modern cipher suites
- Let's Encrypt ACME challenge support
- SSL certificate directory (
nginx/ssl/) with setup instructions - New cookiecutter variables:
include_nginx_service: Include Nginx container in docker-composeinclude_nginx_config: Generate nginx configuration filesuse_nginx: Using Nginx (included or external)use_traefik: Using Traefik (included or external)
Changed¶
- Reverse proxy prompt now offers 5 options:
- Traefik (included in docker-compose) - default
- Traefik (external, shared between projects)
- Nginx (included in docker-compose)
- Nginx (external, config template only)
- None (expose ports directly)
ReverseProxyTypeenum extended withNGINX_INCLUDEDandNGINX_EXTERNAL- docker-compose.prod.yml updated:
- Added nginx service definition
- Services use backend-internal network when nginx is selected
- No ports exposed on backend/frontend when nginx handles traffic
.env.prod.exampleincludes DOMAIN variable for nginx configurationpost_gen_project.pyremoves nginx/ folder when nginx is not selected
Tests Added¶
- Tests for
NGINX_INCLUDEDandNGINX_EXTERNALenum values - Tests for cookiecutter context generation with all reverse proxy options
- Tests for
prompt_reverse_proxy()with nginx choices
[0.1.9] - 2025-12-26¶
Added¶
SQLModel Support¶
- Optional SQLModel ORM as alternative to SQLAlchemy for PostgreSQL and SQLite
- New
--ormCLI option:--orm sqlalchemy(default) or--orm sqlmodel - Interactive prompt for ORM library selection when using
fastapi-fullstack new - SQLModel provides simplified syntax combining SQLAlchemy and Pydantic:
- Full Alembic compatibility maintained with SQLModel
- SQLAdmin works seamlessly with SQLModel models
- All database models updated with SQLModel variants:
User,Item,Conversation,Message,Session,Webhook,WebhookDeliveryVARIABLES.mdupdated with new ORM variables:orm_type,use_sqlalchemy,use_sqlmodel
Changed¶
- Database model templates now support conditional SQLModel/SQLAlchemy syntax
alembic/env.pyusesSQLModel.metadatawhen SQLModel is selected- Repositories remain unchanged (SQLModel uses same AsyncSession and methods)
Tests Added¶
- Tests for
OrmTypeenum values - Tests for
use_sqlalchemyanduse_sqlmodelcomputed fields - Tests for SQLModel validation (requires PostgreSQL or SQLite)
- Tests for
prompt_orm_type()function - Updated
run_interactive_prompts()tests withprompt_orm_typemocks
[0.1.7] - 2025-12-23¶
Added¶
Docker & Production¶
- Optional Traefik reverse proxy with three configuration modes:
traefik_included: Full Traefik setup in docker-compose.prod.yml (default)traefik_external: Traefik labels only, for shared Traefik instancesnone: No reverse proxy, ports exposed directly.env.prod.exampletemplate for production secrets management:- Conditional sections for PostgreSQL, Redis, JWT, Traefik, Flower
- Required variable validation using
${VAR:?error}syntax - Setup instructions in docker-compose.prod.yml header
- Unique Traefik router names using
project_slugprefix for multi-tenant support: {project_slug}-api,{project_slug}-frontend,{project_slug}-flower- Prevents conflicts when running multiple projects on same server
AI Agent Support¶
AGENTS.mdfile for non-Claude AI agents (Codex, Copilot, Cursor, Zed, OpenCode)- Progressive disclosure documentation in generated projects:
docs/architecture.md- layered architecture detailsdocs/adding_features.md- how to add endpoints, commands, toolsdocs/testing.md- testing guide and examplesdocs/patterns.md- DI, service, repository patterns- README.md updated with "AI-Agent Friendly" section
Changed¶
- Template
CLAUDE.mdrefactored from 384 to ~80 lines following progressive disclosure best practices - Main project
CLAUDE.mdupdated with "Where to Find More Info" section - docker-compose.prod.yml now uses
env_file: .env.prodinstead of inline defaults - Removed hardcoded credentials (
changeme) from docker-compose.prod.yml
Security¶
- Production credentials no longer have insecure defaults
.env.prodadded to.gitignoreto prevent committing secrets- Required environment variables fail fast with descriptive error messages
[0.1.6] - 2025-12-22¶
Added¶
Multi-LLM Provider Support¶
- Multiple LLM providers for AI agents: OpenAI, Anthropic, and OpenRouter
- PydanticAI supports all three providers (OpenAI, Anthropic, OpenRouter)
- LangChain supports OpenAI and Anthropic
- New
--llm-providerCLI option and interactive prompt - Provider-specific API key configuration in
.envandconfig.py
CLI Enhancements¶
make create-admincommand for quick admin user creation- Comprehensive CLI options for
fastapi-fullstack createcommand: --redis,--caching,--rate-limiting--admin-panel,--websockets--task-queue(none/celery/taskiq/arq)--oauth-google,--session-management--kubernetes,--ci(github/gitlab/none)--sentry,--prometheus--file-storage,--webhooks--python-version(3.11/3.12/3.13)--i18n- Configuration presets for common use cases:
--preset production: Full production setup with Redis, Sentry, K8s, Prometheus--preset ai-agent: AI agent with WebSocket streaming and conversation persistence- Interactive rate limit configuration when rate limiting is enabled:
- Requests per period (default: 100)
- Period in seconds (default: 60)
- Storage backend (memory or Redis)
Documentation¶
- Improved CLI documentation in README explaining project CLI naming convention (
uv run <project_slug>) - Makefile shortcuts documented with
make helpcommand
Template Improvements¶
- Generator version metadata in generated projects (
pyproject.toml): - Centralized agent prompts module (
app/agents/prompts.py) for easier maintenance - Template variables documentation (
template/VARIABLES.md) with 88+ variables documented
Validation¶
- Email validation for
author_emailfield using Pydantic'sEmailStr - Tests for OpenRouter + LangChain validation (combination is rejected)
- Tests for agents folder conditional creation
Changed¶
Configuration Validation¶
- Improved option combination validation in
ProjectConfig: - Admin panel requires PostgreSQL or SQLite (not MongoDB)
- Caching requires Redis to be enabled
- Session management requires a database
- Conversation persistence requires a database
- Rate limiting with Redis storage requires Redis enabled
- OpenRouter is only available with PydanticAI (not LangChain)
Database Support¶
- Admin panel prompt now appears for both PostgreSQL and SQLite (previously only PostgreSQL)
- Database-specific post-generation messages:
- PostgreSQL:
make docker-db+ migration commands - SQLite: Auto-creation note + migration commands (no Docker)
- MongoDB:
make docker-mongo(no migrations) - Added
close_db()function for SQLite database consistency
Project Name Handling¶
- Unified project name validation between
prompts.pyandconfig.py - Extracted validation into
_validate_project_name()function with clear error messages - Shows converted project name to user when it differs from input
Fixed¶
Backend Fixes¶
- Conversation list API response format: Changed
/conversationsand/conversations/{id}/messagesendpoints to return paginated response{ items: [...], total: N }instead of raw array, fixing frontend conversation list not loading after page refresh - Database session handling: Split
get_db_sessioninto async generator for FastAPIDepends()and@asynccontextmanagerfor manual use (WebSocket handlers) - WebSocket authentication:
- Update
deps.pyto useget_db_contextfor WebSocket auth - Add cookie-based authentication support for WebSocket (
access_tokencookie) - Now accepts token via query parameter OR cookie for flexibility
- WebSocket exception handling: Fix
AttributeErrorwhen exception occurs on WebSocket connection (request.methoddoesn't exist for WebSocket) - WebSocket conversation persistence:
- Fix
get_db_sessionvsget_db_contextusage (async generator vs async context manager) - Fix event name mismatch: backend now sends
conversation_createdto match frontend expectation - Docker Compose: Fix
env_filepath from.envto./backend/.env - ValidationInfo typing: Add proper type hints to all field validators in
config.py
Frontend Fixes¶
- ThemeToggle hydration mismatch: Add mounted state to prevent SSR/client mismatch
- Button component: Extract
asChildprop to prevent DOM warning - ConversationList: Add default value for conversations to prevent undefined error
- New Chat button:
- Create conversation in database immediately (eager creation)
- Clear messages properly when switching conversations
- Fix message appending issue when switching between conversations
- Conversation store: Add defensive checks for undefined state
CLI Fixes¶
- Consistent package name: Changed from
fastapi-gentofastapi-fullstackin version option - Makefile: Always generated now (removed from optional dev tools)
Template/Generator Fixes¶
- Ruff dependency in hooks: Graceful handling when ruff is not installed:
- Check PATH for ruff binary
- Fall back to
uvx ruffif uv is available - Fall back to
python -m ruffif available as module - Show friendly warning if ruff is not available
- Dynamic generator version: Replaced hardcoded version with
DYNAMICplaceholder - Unused files cleanup: Improved post-generation hook to remove:
- AI agent files based on framework selection
- Example CRUD files when disabled
- Conversation, webhook, session files when features disabled
- Worker directory when no background tasks selected
- Empty directories automatically
.envfile location: Move.env.examplefrom root tobackend/
Tests Added¶
- Tests for all configuration validation combinations
- Tests for project name validation edge cases
- Tests for
newcommand--outputoption - Tests for OpenRouter + LangChain validation
- Tests for admin panel prompt with SQLite
- Tests for agents folder conditional creation
- Tests for email validation (config and prompts)
- Tests for rate limit configuration prompts