Versions¶
The main changes in each release of the kavalai package. Releases are
tagged vX.Y.Z in the repository and published to PyPI.
1.0.4 — 2026-09-12¶
Security¶
This release closes several ways in which an agent served to the public could be made to reach internal networks, to disclose what only its operator should see, or to spend without bound. Each change is described in full under the headings that follow.
Server-side request forgery.
http_requestandcrawl_urlrefuse loopback, private, link-local, carrier-grade NAT, multicast, reserved and cloud metadata targets, and any IPv6 address that maps, translates or tunnels to such an IPv4 address.http_requestconnects to the address it checked, so DNS rebinding cannot redirect it, and checks every redirect hop again (kavalai.net). An agent meant to reach an intranet is built withallow_private_networks=True, an argument of the tool factory that the model cannot set.Disclosure to end users.
kavalai.server.public_events()removes node events, token usage and restart reasons from a stream, and replaces a failed run’s error text — which can carry provider request ids, quota state or SQL — with a fixed message quoting the run id.KAVALAI_AGENT_PUBLIC_EVENTSapplies it underpython -m kavalai.server.Unbounded spend per turn.
max_output_tokenscaps what a call may generate, and a response stopped at the cap raisesOutputTruncatedErrorinstead of passing as complete. Anllm_kwargskey the clients do not know, such as a misspelt cap, fails the workflow load instead of being ignored.history_limitandhistory_max_charsbound the history anllmnode sends, andrun_timeoutthe duration of a run.get_chat_historyreturned the oldest fifty messages of a session, so a long conversation paid for a full history on every turn while the model never saw the latest exchange.Fail-open retrieval filter.
source_ids=[]searched the whole collection; it now matches nothing, so a caller that derives the list from what a user may see, and derives an empty one, receives no entries.Least privilege.
provision=Falseon the RAG services issues no DDL, and read paths no longer create the registry, so the role an agent server connects as needs noCREATEright on the RAG schema; collections are created beforehand withcreate_collection().Personal data in the agent database.
record_payloads=Falsekeeps prompts, inputs, outputs and model-call payloads out of the task log, andmax_payload_bytescaps model-call payloads as well as node payloads.AgentService.purge_sessionsanddelete_history_for_sessionblank the model-call payloads of the conversations they remove, so a deleted conversation does not survive as a prompt inmodel_call_stats.Crafted input.
kavalai.textparses HTML and chunks text in linear time, without regular expressions, so a crafted page cannot stall the process that indexes it; its tests bound the time taken on adversarial input.
Added¶
SQLite for the backoffice.
KAVALAI_BO_DB_URIaccepts asqlite:///pathURI, the backoffice migration set applies to SQLite, and a project has adb_type—postgresql(default) orsqlite, in which case itsdb_nameis the agent database file. The project form and the projects page show the fields the chosen type needs.sqlite:///pathis accepted wherever a database URI is:KAVALAI_DB_URIfor the agent server and migrations,DatabaseManager.get_sessionmaker,PostgresRagService.from_uri’s counterpartrag_service_from_uri(), which picks the RAG service from the URI scheme.CollectionRagService(kavalai.rag.collections): the storage model the two RAG services share — arag_collectionsregistry and one table per collection — with the browse methods the backoffice needs (list_collections,get_stats,create_collection,drop_collection,get_embeddings_by_ids) on both backends.modelis optional on both constructors; without one a service browses existing collections.LLMNode.history_limit(default50) andhistory_max_chars: the chat history anllmnode sends is the most recent messages, and at most that many characters of them — whole messages are dropped from the oldest end.AgentService.get_chat_historytakesmax_chars.templates=onrun()andrun_stream(): values for templates the document declares, for one run. Rendering is one pass, so the values need no escaping; they are recorded underrun_templatesinruns.context.timeout=onrun/run_streamandrun_timeout=on the engine cancel a run after that many seconds, parallel branches included, and record it as failed with the newWorkflowTimeoutError. The agent server readsKAVALAI_AGENT_RUN_TIMEOUT_SECONDS.WorkflowEngine(record_context=False)keeps only a run’s input and output inruns.context.rag_querynodes takemin_similarity, and record their hits —id,source_id,similarity,metadata— on the task row and in theoutput_dataofnode_completed, whateverstoresays. The query embedding is reported to the run’s token accumulator.workflow_completedandworkflow_failedevents carryrun_id.kavalai.server.public_events(), an event filter for streams served to people other than the operator (no node events, token usage or restart reasons; a failure’s text replaced by a fixed message quoting the run id), andevent_filter=onstream_sse_events,create_agent_routerandcreate_agent_app.KAVALAI_AGENT_PUBLIC_EVENTSapplies it underpython -m kavalai.server.kavalai.server.sse_responseandAgentRequest[InputT], so a route of a host’s own speaks the same SSE protocol as the router.max_output_tokensonLlmClientParameters, sent under each provider’s own name:max_output_tokens(OpenAI, Gemini),max_tokens(Anthropic, WebLLM),options.num_predict(Ollama). On reasoning models the cap includes the reasoning tokens.KAVALAI_LLM_MAX_OUTPUT_TOKENSsets it for the agent server and the eval judge.OutputTruncatedError, raised when a provider stops at the output cap instead of the partial answer being returned. It is not retried, carries the partial output and token counts, and the truncated call is still recorded. It andLlmClientExceptionare exported fromkavalai.reasoning_effortreaches Gemini, asthinking_level, and Ollama, asthink("none"sendsfalse).dimensions=on the OpenAI and Gemini embedding clients. Bound at registration —register_embedding_provider("openai-512", OpenAIEmbeddingClient, dimensions=512)— it gives a reduced model a name of its own.Four extras dividing
commonby weight:runtime(provider SDKs, MCP, FastAPI, asyncpg, sqlite-vector),webtools(crawl4ai),fastembedandbackoffice(Authlib, itsdangerous, scikit-learn, sse-starlette).commoninstalls all four and keeps its meaning.migrate(set_name, connection=…)runs a migration set on an open connection, inside the caller’s transaction, andmigrate_asyncdoes the same from inside an event loop, with anAsyncConnectionor a URI.kavalai.net, a guard against server-side request forgery for tools that fetch a URL the model chooses:is_public_address(),ensure_public_url()andPublicOnlyTransport, an httpx transport that resolves a host once, checks every address and connects to the address it checked, so DNS rebinding cannot redirect it; each redirect hop is checked again.make_http_request(allow_private_networks=True)andmake_crawl_url(allow_private_networks=True)build the bundled tools for an agent meant to reach an intranet; the switch is an argument of the factory, not of the tool, so the model cannot set it.kavalai.testing, test doubles in the base install that replace the model and nothing else.ScriptedLlmClientanswers from a list of replies or a function, streams each reply through the base client’s streamer, retry and restart handling, reports aModelCallStatfor every call and records the requests it received; the instance is itself an engineclient_factory.FakeEmbeddingClientembeds any text deterministically by hashing its words, andfake_providers()registers both under a provider name for awithblock and restores the registries on exit.kavalai.text:parse_htmlreduces a page to its title, text blocks with their heading paths, links and robots directives in one pass, with the boilerplate rules as parameters;chunk_blocksandchunk_markdownpack blocks into chunks of abouttarget_chars(1200) and never overmax_chars(2000), splitting at lines, sentences and words before cutting. Standard library only, so it runs under Pyodide, and linear in its input.tools/zerocostchatbotuses it, andbuild_index.pygains--target-chars.The chat widget ships in the wheel as
kavalai.widget(kavalai/widget/kaval-chatbot.jsand.css, moved fromchatbotwidget/);widget_dir()andasset_path(name)locate the installed files for a Python host, and Chat widget documents it. New options:textsfor every visible string, aria labels and error messages included; a disclosure label,AI assistant, on by default (disclosure: falseremoves it);onFeedback(runId, vote, comment)with thumbs and an optional comment box;widget.on(...)events (open,close,reply,error,resize,feedback);agentConnectorheaders(an object or a function),onResponse,setConversationIdandstorage("session","local","none"); the reply carries therunIdofworkflow_started;--kcb-font-familyand--kcb-font-size.delete_many(ids, collection_name=None)on every RAG service (one statement per collection on the SQL backends; a loop overdeleteby default), and, in the optional tier,delete_by_metadata(collection_name, match)andreplace(collection_name, texts, metadata_list, source_ids=None, *, match=None, source_id=None). Amatchis equality on top-level keys with scalar values, served bymetadata @> :matchon PostgreSQL.replaceembeds first, then deletes and inserts in one transaction, so a failure leaves the old rows; emptytextsdeletes the selection.min_similarity=onqueryandquery_batch.stats_receiver=onquery,query_batch,index,index_batchandreplace, and on the RAG service constructors as the default. Each embedding call’sModelCallStatgoes to it; arag_querynode passes the run’s receiver.provision=Falseon the RAG services: no DDL; a missing registry reads as empty and indexing into a missing collection raisesRuntimeErrornamingcreate_collection().ensure_registry()is public, andcreate_collection(name, dim, *, model=None, vector_type=None)issues DDL whateverprovisionsays.vector_type="halfvec"onPostgresRagService(pgvector 0.7 or later): 16-bit embeddings and HNSW indexes for up to 4,000 dimensions. The type is read back from the collection’s column.Filtered PostgreSQL queries enable
hnsw.iterative_scan = relaxed_orderfor their transaction on pgvector 0.8 or later, so a filter whose rows lie away from the query still returnstop_krows.rag_service_from_uriforwards further keyword options to the service.KAVALAI_RAG_MODELregisters thedefaultRAG service when the agent server starts, over the index atKAVALAI_RAG_URI(a Postgres URI orsqlite:///file) in the optionalKAVALAI_RAG_SCHEMA, with the normalizer fromKAVALAI_EMBEDDING_NORMALIZER_YAML; a deployment with one index needs no setup module. The URI is required with the model: the index is not assumed to live in the agent database.model_call_stats.session_idandrun_id(agents migration0005): every model call a run makes — the query embedding of arag_querynode included — is attributed to its agent, session and run, so cost per run and per conversation is a query.TaskLogger.log_model_call,TokenAccumulator,StatsBridgeandAgentService.add_model_call_statstakesession_idandrun_id;get_model_call_statsfilters byagent_id,session_idandrun_id;MemoryTaskLogger.model_callsholdsModelCallRecordobjects carrying the ids, withmodel_calls_for_run;SqliteTaskLogger’s own table gains the columns andget_model_calls(run_id)filters by them.record_nodesandrecord_payloadson every task logger: record no node rows, or keep names, timings, errors and token counts but not the prompts, inputs, outputs and model-call payloads.write_nodeandwrite_model_call, the public hooks a task logger backend implements, andTeeTaskLoggerto put a logger of one’s own — a meter, say — beside the database one.AgentService.list_sessions(agent_ids, *, search, external_id_prefix, exclude_external_id_prefix, start, end, limit, offset)— the backoffice list’s query, scoped to several agents at once — andAgentService.purge_sessions(before, agent_ids=None, batch_size=1000), which deletes idle sessions in batches, yields each batch’s ids, and blanks the purged sessions’ model-call payloads while keeping their token counts.Backoffice:
GET /projects/{project_id}/llm-call-statsacceptsagent_id,session_idandrun_id. The Model calls page links each call to its conversation and to its run’s tasks, filters by therun_idandsession_idquery parameters, and is reached from a conversation’s Model calls button. The task debugger lists the run’s model calls — tokens and duration per call — beneath its tasks.GET /projects/{project_id}/rag/collectionsreturns each collection’s name, model, dimension, schema version and entry count.
Changed¶
An
llm_kwargskey that is not a field ofLlmClientParametersfails the workflow load, naming the key and listing the valid ones, with a hint towardsmax_output_tokensformax_tokens,max_completion_tokens,num_predictandmax_new_tokens; a value of the wrong type fails too. Such keys were ignored before.workflow.clients.build_parametersrefuses them as well.A
restartevent is emitted only by a node that streams; a node that streams nothing has no partial output for a client to discard.A run that fails with a
WorkflowExceptionis recorded as failed on its run row, as other failures already were.A RAG service registered by name is built once per engine and kept, instead of on every
rag_queryexecution.The OpenAI embedding client records the response’s
modelandusageonly; the vectors are no longer copied intomodel_call_stats.The OpenAI client sends a structured-output schema as
text.format, so the SDK no longer validates a truncated answer before the stream reports why it stopped. An OpenAI response that is incomplete for another reason, and an Anthropic stop atmodel_context_window_exceeded, raiseLlmClientException. An error a client raises as aRuntimeErrorsubclass reaches the stream consumer as that type.Migrations run over the runtime’s async drivers — asyncpg for
postgresql://, aiosqlite forsqlite://— through Alembic’srun_syncrecipe, instead of converting the URI to psycopg2. A URI that names a synchronous driver is run on that driver.migrate()called inside a running event loop runs on a worker thread. Itsschema,skip_create_schemaandmax_waitare keyword-only, and SQLite is no longer waited for.The agent image installs
kavalai[runtime](--build-arg EXTRAS=…for more) and the backoffice imagekavalai[runtime,backoffice].Install hints name the narrowest extra, e.g.
pip install "kavalai[runtime]"; the agent server, the backoffice, its embedding projector, FastEmbed and the crawl4ai tools fail with a hint instead of a bareImportError.http_requestrefuses private, loopback, link-local and cloud metadata targets withUnsafeUrlError, and is a coroutine function. The guarded client ignoresHTTP_PROXY/HTTPS_PROXY; withuse_proxy=Trueonly the URL pre-check applies, since the proxy resolves the name.crawl_urlrefuses such a URL withsuccess=Falseand aRefused:message, and withholds the content when crawl4ai reports a final URL that fails the check.The widget no longer retries a turn the server has started: once an SSE frame has arrived, a broken stream ends with the
interruptedtext instead of sending the message again and paying for the run twice. Statuses below 500 are not retried either.source_ids=[]matches nothing:queryreturns[]andquery_batchone empty list per text, without embedding;Nonestill means no filter.index_batchwithsource_ids=[]and non-empty texts raises the length error. A query on a collection that does not exist returns[]without embedding.The registry’s model is authoritative: an existing collection is queried and indexed with the model recorded for it, one embedding client per model. The constructor’s
modelis the model for new collections; a mismatch logs a warning. A service built withmodel=Nonecan query and index existing collections and refuses only to create one.Read paths (
list_collections,get_stats,count_entries,query, the deletes) no longer create the registry.When two processes create one collection at once with different dimensions or models, the one whose registry row lost raises
ValueErrorinstead of carrying on.examples/ragindex/index_csv.py --replaceusesreplace, one source per transaction.--indexinindex_csv.pyandquery_index.pyis a database URI or a SQLite file path; thepostgresshorthand, which readKAVALAI_DB_URI, is gone.sessions.updated_atis moved by every run and so records a session’s last activity; before, nothing updated it after the session was created. Migration0005backfills it from each session’s last run, adds composite indexes —sessions(agent_id, external_id),sessions(agent_id, updated_at),sessions(updated_at),chat_messages(session_id, created_at),chat_messages(agent_id, created_at),model_call_stats(agent_id, created_at)— and drops the single-column indexes they replace.SQLITE_SCHEMA_VERSIONis5.A task logger’s
max_payload_bytescaps model-call payloads as well as node payloads.AgentService.delete_history_for_sessionalso blanks the session’s model-call payloads.kavalai.backoffice.sessions.get_sessions_summarydelegates tokavalai.agent_service.summarise_sessions(), whereSessionSummarynow lives.Backoffice: the conversation list is ordered by last activity — the time of a session’s most recent run — and labels its timestamp accordingly.
POST /projects/{project_id}/rag/queryno longer requiresmodel: an existing collection is embedded with its recorded model, which the PCA projection uses as well. The RAG explorer shows the selected collection’s recorded model instead of asking for one, and drops All Collections, since a query searches exactly one collection.SqliteRagServiceuses the shared storage model: a registry and a table per collection, each with its own embedding dimension, instead of a singlerag_indextable with one dimension per file. A file in the old layout is refused with a message asking for the index to be rebuilt; thetable_nameconstructor argument is gone.collection_name=Nonenow means"default", as on Postgres, rather than every collection.The backoffice session list no longer depends on Postgres-only SQL (
DISTINCT ON,jsonb_typeof):json_typeof()andjson_array_length()render per dialect and a window function ranks the messages.Backoffice migration
0002runs in batch mode, and UUID columns in the backoffice set useuuid_column(), so the set applies on SQLite as it does on Postgres.
Removed¶
kavalai.llm_clients.kwargs_mapper, which nothing imported and which disagreed with the clients.psycopg2-binaryfrom every extra, andkavalai.migrate_db.ensure_sync_scheme.The RAG services no longer write embedding statistics into
model_call_statsthemselves. A standalone indexing job that wants the record passesstats_receiver=StatsBridge(task_logger, agent_id).PostgresRagService’sagentparameter.The private task logger hooks
_log_node_impland_log_model_call_impl(nowwrite_node/write_model_call), andkavalai.workflow.tasklog.postgres._to_orm_stat.chatbotwidget/at the repository root; the widget lives inkavalai/widget/.tools/zerocostchatbot/htmlmd.pyand the tool’s own chunker, replaced bykavalai.text.
Fixed¶
AgentService.get_chat_historyreturned the oldestlimitmessages of a session instead of the most recent ones, so a long conversation lost its latest turns from the model’s view.A response cut off at the output limit was returned as if complete: partial text, or for structured output JSON repaired into a model with content missing. Every client now checks the provider’s stop reason.
Gemini usage without a candidate token count (a cap spent on thoughts alone) no longer fails the stats record.
The wheel carries
default_prompt_template.j2;Agent()from an installed 1.0.x wheel raisedFileNotFoundError.sse-starlette, imported by the backoffice, is declared rather than arriving throughmcp; the deaddemo_agents/**package-data globs are gone.Widget styles: the button reset no longer overrides the send button, launcher, greeting and choice chips, and the
hiddenattribute is honoured, so the typing indicator and a closed window no longer show. Conversation ids are generated on pages without a secure context.A
normalizergiven to a RAG service was never applied: the services passed it to the embedding client without asking for normalisation. It is now applied on the index side and on the query side alike.SQLite
delete_by_metadatadoes not treat JSONtrueas the number 1, matching PostgreSQL.The Postgres insert casts embeddings to the schema-qualified
public.vectortype, as the query always did.
Upgrading¶
A workflow whose
llm_kwargscarries a key the clients do not know — most oftenmax_tokens— no longer loads. Rename it (max_output_tokens) or remove it; it had no effect before.Code that used psycopg2 because
kavalai[common]happened to install it — a sync engine on apostgresql://URI, for instance — must now depend onpsycopg2-binaryitself, or move to asyncpg.python -m kavalai.migrate_dbneeds neither change: it uses asyncpg, and an explicitpostgresql+psycopg2://URI still works where psycopg2 is installed.A service that installed
commononly to serve workflows can installkavalai[runtime]; addfastembedfor local embedding models andwebtoolsforcrawl_url/web_search.An agent that must reach an intranet through
http_requestorcrawl_urlregistersmake_http_request(allow_private_networks=True)/make_crawl_url(allow_private_networks=True)instead. A direct synchronous call ofhttp_requestnow returns a coroutine.Pages that load the widget from
chatbotwidget/load it fromkavalai/widget/(or from the installed package, throughkavalai.widget.widget_dir()).PostgresRagService(session_maker, model=None, *, normalizer=None, schema=None, provision=True, stats_receiver=None, vector_type="vector"): the positionalagentparameter is gone and every option aftermodelis keyword-only. Passnormalizer=by name; replaceagent=withstats_receiver=StatsBridge(task_logger, agent_id)where the statistics are wanted.model=on a RAG service now names the model for collections it creates. Existing collections are embedded with their recorded model, and a service without a model can query them, where it used to raise.An index built under 1.0.3 by a RAG service that had a
normalizerholds unnormalised vectors, while its queries are now normalised. Rebuild such an index, or construct the service without the normalizer.A caller that computes
source_idsand may produce an empty list now gets no hits for it, rather than a search of the whole collection. PassNonewhere “no restriction” is meant.Run the agents migrations (
python -m kavalai.migrate_db agents) for revision0005. It rewritessessionsonce. On a large PostgreSQL database the indexes can be built beforehand withCREATE INDEX CONCURRENTLYunder the names above, and the revision then leaves them. Embedding vectors that earlier releases copied intomodel_call_stats.response_dataare not removed by the migration; clear them when convenient withupdate model_call_stats set response_data = null where call_type = 'embedding' and response_data is not null;.A task logger subclass overriding
_log_node_impl/_log_model_call_implimplementswrite_node/write_model_call(stats, *, agent_id, session_id, run_id)instead. Code that imported_to_orm_statto store statistics passes the PydanticModelCallStattoAgentService.add_model_call_statsor to aStatsBridge.Browser (Pyodide) SQLite databases are recreated on first use, since
SQLITE_SCHEMA_VERSIONmoved to5.
1.0.3 — 2026-08-28¶
Added¶
Provider registries (
kavalai.llm_clients.registry):register_llm_provider(),register_embedding_provider()andregister_rag_service()accept a class, a dotted path or a callable, so a third-party client is one registration away.BaseLlmClient,BaseEmbeddingClientandensure_user_turnare exported for that purpose, andKAVALAI_PROVIDER_MODULESloads such modules at start-up.rag_queryworkflow node andWorkflowEngine(rag_services=…); a node resolves its service as node → graph → “default”.parallelworkflow node with concurrent branch execution.Engine lifecycle:
await engine.connect()/await engine.aclose(), or the async context manager, open and release the MCP sessions once per engine.Agent(allowed_tools=…)andallowed_toolsonagentnodes, with"*"andproto://server.*patterns.Fleet-wide model defaults:
WorkflowEngine(default_llm_model=…, default_llm_parameters=…), filled by the server fromKAVALAI_DEFAULT_LLM_MODELand theKAVALAI_LLM_*variables.Evaluation package
kavalai.eval(SimpleEvaluator,JudgeEvaluator, YAML case files) and thekavalai-evalconsole script.Six agent skills shipped in the wheel and installed by
kavalai-skills install.gpuextra (fastembed-gpu) for local embedding on an NVIDIA GPU.Key-free
web_searchtool over DuckDuckGo, and acrawl4aiCompose service.KAVALAI_AGENT_SETUP_MODULEregisters tools and RAG services before the agent server loads its workflow.Migrations:
model_call_statsrecordscached_prompt_tokensandreasoning_tokens;tasksrecordsseq,parent_task_nameandtool_uri;users.active_project_idis cleared when its project is deleted.Documentation: quickstart, architecture, comparison, serving, guides, reference, cookbook and deployment pages; examples
green_village,bakery,business_info_agent,ragindexandchat_client.
Changed¶
Breaking. Packaging extras collapsed to
common,common_web,gpu,testanddocs; the per-provider extras (openai,gemini,anthropic,ollama,rag,postgres,mcp,server,tools,all, …) are gone. Install withpip install "kavalai[common]".Breaking. Environment variables renamed:
GOOGLE_OAUTH_CLIENT_ID/_SECRET→KAVALAI_BO_GOOGLE_CLIENT_ID/_SECRET,FRONTEND_URL→KAVALAI_BO_FRONTEND_URL,BACKOFFICE_HOST/_PORT→KAVALAI_BO_HOST/_PORT,KAVALAI_LLM_TIMEOUT→KAVALAI_LLM_TIMEOUT_SECONDS,TOR_PROXY_HOST/_PORT→KAVALAI_TOR_PROXY_HOST/_PORT.KAVALAI_BO_SESSION_SECRET_KEYis required and has no fallback.create_model_call_stat(duration_sections=…)renamed toduration_seconds.The Gemini client retries only HTTP 429; 400, 401 and 403 raise immediately.
Integration tests carry the
integrationmarker and are deselected by default.
Removed¶
Breaking.
KAVALAI_OPENAI_SERVICE_TIER— useKAVALAI_LLM_SERVICE_TIERorllm_kwargs.kavalai.tools.websearch(Serper, LangSearch, Google Custom Search) and their API-key variables; the RSS tool.costandcurrencycolumns onmodel_call_stats(see Observability for the reason).KAVALAI_DEFAULT_EMBEDDING_MODEL.kavalai/tools/index_csv.pyandcli_chat.py— nowexamples/ragindexandexamples/chat_client.
Fixed¶
PostgresTaskLoggerdroppedcached_prompt_tokensandreasoning_tokens.A stale
active_project_idafter a project was deleted or a member removed made every project-scoped backoffice endpoint answer 403.The backoffice sessions page issued one query per session.
1.0.2 — 2026-08-11¶
Added¶
AnthropicClient.SqliteRagService— a sqlite-vector file index that also runs in the browser — behind theBaseRagServiceinterface shared withPostgresRagService.Streaming:
WorkflowEngine.run_stream()yieldingWorkflowStreamEvent,POST /stream_agenton the agent server, per-nodestream_delta/stream_instructions/stream_partialsflags andstream_timeout_secondsonLlmClientParameters.Alembic migration sets
agentsandbackofficereplace the plain-SQL scripts.
Changed¶
Breaking. Package layout flattened:
kavalai.agents.*moved to the top level (kavalai.agent,kavalai.db,kavalai.server, …).Breaking.
LlmClientParametersno longer defaultstemperatureandtop_p; the provider’s defaults apply.All workflow persistence goes through
AgentService.
Removed¶
Breaking.
kavalai.workflow.storage(DataStorage,RunHandle,InMemoryDataStorage,SqliteDataStorage) and theRagServiceclass, replaced byAgentServiceand the RAG services above.
1.0.1 — 2026-07-06¶
First release on PyPI.
A minimal, Pyodide-compatible core with optional extras; Python 3.12 or later.
LLM clients for OpenAI, Gemini and Ollama behind one streaming interface, a FastEmbed embedding client, and
BrowserLLMClient(WebLLM) for execution in the browser.The planning agent and function kernel (Python, REST and MCP tools).
The workflow engine with
start,end,llm,agent,function,ifandswitchnodes, YAML definitions and SVG rendering.PostgreSQL/pgvector RAG service.
The agent REST server and the backoffice (FastAPI and Angular) with agents, conversations, RAG, workflow and task pages.
The documentation site.