Changelog
What's new.
Every improvement to the platform, as it ships.
· MCPGatewayService
Rag_search takes audience PUBLIC, the search the public may read (SP95 T12)
An AI on a phone call answers an anonymous caller, so it searches only the published articles the public may read, never notes (spec D12). The optional audience enum is ["PUBLIC"] and is forwarded to RAGService (Task 11); any other value is INVALID_ARGUMENTS by schema and never reaches RAG.
· KlusterServices
Rspamd keeps its data in its own Redis
local.d/redis.conf is now the redis-own.conf key: rspamd-redis in mail, database 0, the same mail_rspamd login. The subPath change rolls the pod. The old key stays in the ConfigMap until the switch is confirmed, so `kubectl -n mail rollout undo deployment/rspamd` is the whole rollback (SP98-M-1, stage 2 of 3). Ship it only after rspamd's data has been copied into rspamd-redis.
· KlusterServices
A Redis of rspamd's own
rspamd parses untrusted mail and logs in to the platform Redis, beside every member's session, and no ACL can keep it from them there: a user cannot be scoped to one database; rspamd's scripts pass class labels, flags and msgpack blobs as KEYS, so a key-pattern ACL refuses every Bayes learn; and its Bayes expiry SCANs, which lists every key name (*** and OTK names are bearer credentials) whatever the patterns. This adds rspamd-redis in mail: the platform Redis's build, pinned to k1m1 beside rspamd with its files on that node's disk, one database, PING only without a login, mail_rspamd with rspamd's existing password, kamo_ops for operators, and a NetworkPolicy that admits rspamd's pods alone. CI applies it and waits for it before rspamd. rspamd itself is unchanged until the next commit (SP98-M-1, stage 1 of 3).
· SecurityService
A charter save reaches the AI runtime at once, and the charter profile names the company (SP95 T6)
**************** now carries a cause. The rights path sends RIGHTS; a committed charter save sends CHARTER for each affected AI member, with the member's current rightsStamp (read after the commit, in one query) and empty added/removed, so every kamoai pod drops that AI's charter and tool lists at once instead of serving a stale tool list. - A member-level save (S6) announces the member when it is an AI. - A role, department or job-title save, charter-only (S7-S9) or a full update carrying charterItems, announces every non-retired AI team member of the org, from one org-keyed query. - CharterProfile gains orgName, the spoken company name (OrgSpokenName: short name, else display name, else legal name), for the brief's identity line.
· MCPGatewayService
Approvals_request sends a class a tool performs to that tool; the charter stamp keeps tools fresh (SP95 T10)
approvals_request refuses an action class a catalogue tool performs with INVALID_ARGUMENTS USE_TOOL {tool, tools, actionClass}, or NO_TOOL_FOR_YOU when the caller can see none of those tools; only DECISION_ONLY classes still get a decision-only approval. ToolSpec.alsoClasses declares the classes a planner may re-class to (mail, chat, calendar, tasks, phone_call), the registry derives the class -> tools table from them, and visibility hides a tool only when every class it may take is FORBIDDEN. The pipeline now refuses a FORBIDDEN planned class on an approved re-execution too, and CharterPolicy mirrors D1 (an unnamed PLACE_INTERNAL_CALL inherits a FORBIDDEN PLACE_CALL). X-Kamo-Charter-Stamp reaches CharterClient.policy(memberId, expectedStamp), which re-reads a cached charter under another stamp at once; tools/list is judged by one charter read and answers ****************
· kamo-apps
Open the application with the answers a lender's site already took
A lender's marketing site can now carry its own short "get started" form and send the borrower here with purpose, name, email and phone filled in, so nobody types them twice. The answers travel in the URL fragment, never the query string: a fragment is not sent to the server, so a borrower's email never lands in an access log or a Referer, and the form wipes it from the address bar as soon as it has read it. The purpose travels as the enum name (purchase, refinance) and is matched against the product's own options, so the site never needs an ordinal id. Seeding saves nothing: on a contact-first product the lead is still created by the borrower's own first "continue", as if they had typed the answers.
· SecurityService
SP94 DDL for the org's two phonetic name columns, pinned by a contract test
orgs.title_phonetic and orgs.title_short_phonetic, both nullable VARCHAR(255) with no default and no CHECK, the same shape as users.name_phonetic. Hand-applied to production (SP94 Task 2); the contract test pins the file to spec 5.1's one statement.
· AIService
E5 speaks with an AI member's remembered voice, its preferred model first, and names the voice it sent
For an AI subject (the AI's own session, or the internal twin with ?memberId=) every TTS candidate is sent the voice the SP96 resolver decides, and the answering model's decision is remembered after the synthesis (insert, compare-and-set, hourly touch). With model kamo:auto the member's preferred model is walked first. A person's session, including one sent as an AI actor, keeps voiceFor unchanged. Memory is best-effort: a failed read or write is a WARN and the member still speaks. Every speech answer carries X-Kamo-Voice-Id.
· RAGService
Audience PUBLIC narrows a session search to published public articles
A session search may send audience: "PUBLIC" to search as the public widget does (no member, no member type): published articles flagged for the public, never a note. An AI member on a phone call searches with its own TEAM_MEMBER session while an anonymous caller steers the questions. The field only narrows: any other value is 400 {error, field:"audience"}. The internal path ignores it. KamoAI SP95 Task 11, spec §5.5, D12.
· VOIPService
The phone server's team line for an AI member's unnamed transfer
SP95 D10: the AI voice settings take defaultTransferExtension (digits, like the fallback; blank clears it) and echo it. The new internal route GET **************** answers the team line's SIP target, or 404 NO_TEAM_LINE when it is unset.
· SecurityService
A colleague's call card for AI calls, and consent to AI calls on one's own profile (SP95 T5)
- GET **************** (X-Internal-Auth): a team member's name, kind, whether they are still here, their effective zone (work site, member, org, UTC), the E.164 numbers on their profile (CELL, PHONE, WORK) and their own standing consent. A member who is not on the team (a portal login) has no card. - GET/PUT **************** a person switches AI team members' calls on for one of their own numbers, or off; a ledger record under member:<id>, via MEMBER_PROFILE. Self or MANAGE_MEMBER_SECURITY reads (every number masked); only the person themself writes. - MemberZoneResolver: TimezoneController's resolution, extracted unchanged, so the member's own zone and an AI's calling hours for them agree.
· AIService
Computer use is earned through Kamo's own computer_* tools by any provider (SP98 T25A)
native computer tool with a beta header and passed on any 2xx. AI members drive their workstation through the Tool Plane's computer_* tools, never a vendor's native API, so the check now tests exactly that, the same for every provider type whose chat adapter sends images: three 1280x800 test screens, a named button on each, clicked through computer_click(x, y) inside that button. A pass writes toolVersion kamo_computer_1, the contract the model passed. - The router serves COMPUTER_USE where the adapter sends images and replays tool results (a tool loop over screenshots), whoever the vendor. (UI-TARS, GUI-*, a computer-use model) are hinted for the check to settle. - An AI may still use COMPUTER_USE only once it is VERIFIED.
· MCPGatewayService
Phone_call calls a colleague by memberId, as PLACE_INTERNAL_CALL (SP95 T9)
A colleague is named by memberId, never by number: the planner reads their SecurityService call card and dials the cell, else the phone, else the work number on their profile (a named to must be one of theirs). Only an active person of the caller's org can be called this way; refusals are INVALID_ARGUMENTS NOT_A_COLLEAGUE, CANNOT_CALL_SELF, NO_NUMBER_ON_PROFILE or NOT_THEIR_NUMBER. The call is PLACE_INTERNAL_CALL, its summary names the colleague and carries no number, and K6 gets calleeMemberId. The callee's own verified request, handed over on the default plan as a VerifiedRequest, is the call's consent (consentBasis CALLEE_REQUEST with its requestRef). A call by number stays PLACE_CALL, unchanged. ActionClasses gains PLACE_INTERNAL_CALL.
· kamo-internal
SP96 sex on the member profile, gated as personal data
A Personal details card in the Identity room holds the member's sex: Not specified, Female or Male. It is on the member row, so the gates mirror SecurityService's D3 rule: the member and their administrators see it, and on an AI so do its workforce managers and its supervisor. A person changes their own, an AI never does. Not specified saves as null. The key rides a save only when it was changed, because SecurityService refuses it from anyone who may not write it (SP96-T5-1). A refusal that names a field now shows under that field instead of under every field in the same request.
· kamo-internal
SP96 sex on the member profile, gated as personal data
A Personal details card in the Identity room holds the member's sex: Not specified, Female or Male. It is on the member row, so the gates mirror SecurityService's D3 rule: the member and their administrators see it, and on an AI so do its workforce managers and its supervisor. A person changes their own, an AI never does. Not specified saves as null. The key rides a save only when it was changed, because SecurityService refuses it from anyone who may not write it (SP96-T5-1). A refusal that names a field now shows under that field instead of under every field in the same request.
· kamo-shared-library
The computer-use check writes the contract a model passed (SP98 T25A)
COMPUTER_USE is earned through Kamo's own computer_* tools by any provider, so toolVersion is probe-filled: the check records which contract the model passed, and an admin who adds the capability has no vendor tool version to type.
· kamo-shared-library
SP96 retire the charter's VOICE TTS pin
A VOICE item is style only: validation refuses voiceModelId/voiceId (FIELD_NOT_ALLOWED), the resolver has no TTS key and EffectiveCharter no Tts, and CharterView emits no voice.tts. AIService remembers each AI member's voice per model instead. The columns and entity mappings stay; a row still holding a pin is emitted as its style, and saving it clears the pin.
· kamo-shared-library
PLACE_INTERNAL_CALL, member consent, the spoken company name, the team line, ON_REQUEST approvals
SP95 Task 4 (spec §5.2, D1, D4, D10, D15, D24): - AiActionClass gains PLACE_INTERNAL_CALL (AUTONOMOUS), appended last. CharterResolver resolves an unnamed PLACE_INTERNAL_CALL FORBIDDEN, with PLACE_CALL's source, when PLACE_CALL resolved FORBIDDEN. The APPROVAL section cap follows the class count (19). - AiVoiceConsentVia gains MEMBER_PROFILE; **************** - OrgSpokenName: the short name, else the display name, else the legal name. - **************** (DEFAULT_TRANSFER_EXTENSION, nullable). - AiCallEventType gains CONSENT_BASIS. - AiApprovalMode gains ON_REQUEST; AiApproval.requestRef (request_ref VARCHAR(200)). The two new columns must exist (SP95 Task 3's DDL) before this reaches main.
· AIService
An AI member's remembered voice per speech model, and the resolver that keeps or re-picks it
The ai_member_voices store (JDBC, TransientDbRetry, compare-and-set re-picks, an hourly touch, the CHOSEN upsert and the one-statement preferred flag), the member facts it needs (AI, sex, language), and the pure resolver of SP96 spec section 5.4: PASS, USE, KEEP or PICK per candidate, by language then gender tier, anchored on the preferred voice's
· VOIPService
The DDL file for the phone server's team line
SP95 Task 2. **************** a nullable VARCHAR(20) that holds the extension an AI's unnamed transfer rings (the team line, 8200 on the k1m1 FreePBX). Hand-applied by Task 3 before the builds that map it; Sp95DdlContractTest pins the file to the spec's one re-runnable, additive, nullable statement.
· SecurityService
SP96 hire options to leave out the phone extension and the workstation
An AI hire's create body takes aiPhoneExtension and aiWorkstation (JSON booleans, true when absent). A step left out is created SKIPPED with NOT_REQUESTED_AT_HIRE, so the hire still reaches COMPLETE, and the retry route reopens it as Set up now. The checklist view marks such a row skippedAtHire and retryable. A retire relabels it with the retire reason, so it can never be set up on a retired AI.
· AIService
A voice's gender, from its record, a curated table or its style, and vendors' genders kept by the probe
SP96 Task 7. VoiceGender {FEMALE, MALE, NEUTRAL, UNKNOWN} with a lenient parse, and VoiceGenders.of(type, model, record) applying spec 5.3 in order: the convention, Piper norman, Deepgram thalia), a style of exactly female/male, else UNKNOWN. VoiceInfo gains gender: ElevenLabs maps labels.gender, none. ProbeRunner stores gender with the other voice fields. Nothing reads the gender yet (Task 8).
· SecurityService
SP96 sex on the member profile and at creation, gated as personal data
GET /api/security/members/{id} names sex (FEMALE, MALE or null) only to a caller who may read it (MemberSexAccess, spec D3); the key is absent for anyone else. PUT refuses a bad value (400 field sex) and a caller who may not write it (403 NOT_ALLOWED field sex) before anything is written, sets it, and audits a change by someone other than the member: an AI's with the values, a person's without them. Member creation reads sex from the body for any record through the same MemberSexWire.
· kamo-shared-library
SP96 MemberSex, Member.sex and a gender on the voice record
members.sex is a nullable STRING column (FEMALE | MALE, null = not specified), so an AI member can speak with a matching voice. The TTS and REALTIME_AUDIO voices record accepts an optional gender field.
· AIService
The ai_member_voices table and the charter voice-pin migration, as hand-applied SQL
SP96 Task 2. The table remembers an AI member's voice per TTS model (spec 5.1); the migration moves every charter VOICE pin to a CHOSEN, MIGRATED, preferred row and clears only a pin whose exact voice is now remembered (spec 5.9). It is run once by Task 11, after Task 10 has deployed. Sp96VoiceDdlContractTest keeps both files re-runnable, additive and exactly the contract.
· SecurityService
SP96 DDL for members.sex, pinned by a contract test
A member's sex is a nullable VARCHAR(16) (FEMALE | MALE, NULL = not specified), hand-applied in SP96 Task 3. The contract test keeps the file re-runnable, with no NOT NULL lacking a DEFAULT and no CHECK, JSON or DROP.
· kamo-internal
The voicemail list updates live (SP98-Q-3)
Nothing in the browser subscribed to /topic/voip/voicemail/<member>, where MediaService relays VOIPService's new-voicemail frame, so the card arrived and the list did not change. Every tab now listens there on the chat socket it already holds (the call socket is only open in the tab that owns the line, and only for an org that can take calls), and folds the mailbox's first page into the list, keeping the pages already scrolled into. A reconnect catches up the same way. The dead VOICEMAIL_DEPOSITED branch on the SMS topic is gone.
· TranslateService
Send en->ta to Kamo IndicTrans, and log which engine answered
No provider claimed en->ta (LibreTranslate lists no Tamil, Bergamot serves German only), so the router returned the English source for every Tamil request, and the dictionary sync wrote it as the value: kamo-internal's ta.json equalled English in 34,549 of 34,779 keys. IndicTransProvider claims exactly en->ta, the pair the klusterservices indictrans/ pod serves; ta->en would need the indic->en model, which is not deployed, so it stays unclaimed. It speaks the engine's LibreTranslate-shaped API behind the same auth proxy token, with LibreTranslateProvider's retry policy (a 4xx other than 429 is final; a 503 busy is tried again inside a 100 s budget). The router tries it after Bergamot and before LibreTranslate. ProviderRouter now logs one line per translation: the pair, the provider that actually answered, the sizes and the time, never the text ("translated en->ta provider=indictrans chars=21->34 ms=412"). translate.indictrans.base-url goes into application.yml and the k8s ConfigMap, which replaces it in production.
· KlusterServices
An English-to-Tamil engine, so Tamil stops being English
LibreTranslate lists no Tamil and Bergamot serves German only, so every en->ta request (the dictionary sync, Help Center articles, chat) matched no provider and came back as its English source: kamo-internal's ta.json equalled English in 34,549 of 34,779 keys. Kamo IndicTrans serves IndicTrans2's distilled en->indic 200M model (MIT; the rotary-position variant by its authors, as converted to CTranslate2) on CPU on k3m1, behind the same Kamo auth proxy as bergamot and libretranslate (kamo-translate-auth). It speaks LibreTranslate's API and serves en->ta only; any other pair is a 400, never an English echo. - scripts/server.py: IndicTransToolkit's IndicProcessor for an English source, ported; Kamo's KMPH<n> sentinels ride as the model's own <ID n> entities (150 of 150 kept); and four output repairs measured on kamo-internal strings: spaced dashes kept, the model's visarga-for-colon (Tamil aytham) turned back into a colon, the ellipsis character kept, and no full stop added to a label. Logs sizes and timings, never text. SIGTERM finishes the requests in flight. - scripts/fetch_models.py: Job indictrans-models fetches the model by revision and SHA-256 and the packages by requirements.txt with --require-hashes into a local-path PVC; a file that does not verify never takes the served name. The serving pod mounts it read-only and has no egress (CiliumNetworkPolicy); only translateservice reaches the proxy. - Sizing (measured): 0.9 GB peak RSS, 0.5-0.6 GB serving; p50 0.16 s per string at 4 threads. Requests 2 CPU / 1Gi, limit 2Gi, no CPU limit. - Workflow step Deploy IndicTrans (K3M1), before the audit; a changed script or proxy config restarts a running server. SECRETS.md lists the new consumer of kamo-translate-auth. Tests: python3 -m unittest discover -s indictrans/tests (70; the two real-model tests run with INDICTRANS_MODEL_DIR set).
· kamo-register
A visitor handed over from another apex as ?vid= keeps their identity
www.kamocrm.com's sign-up now opens register.kamouniverse.com (owner, 2026-09-28). Its kdsc_vid cookie is scoped to .kamocrm.com and can't follow, so kamo-marketing carries the GUID as ?vid= next to ?discount= (app/lib/register-link.ts). resolveMarketingTracking now adopts a well-formed carried GUID, and persists it with the code on this apex, instead of minting a new identity. The signup then lands on the same visitor timeline the referring member sees. It is adopted only alongside a code, a cookie identity on this apex still wins, and a malformed value is never adopted.

Your universe is waiting.
Start free in minutes. Add products whenever you are ready — no migration, no new logins.
Free forever — no credit card required