Engineering
Software Design Specification
How Kavanah is designed — decomposition, request lifecycle, data model, AI agent runtime, extensibility, and the quality gates that hold it together. Written for engineering reviewers performing technical due diligence.
Last updated: July 28, 2026
Purpose & scope
This document describes the design of the Kavanah platform: how responsibilities are divided, where enforcement points sit, and which properties are guaranteed structurally rather than by convention. It sits one level below the Architecture & Technical Overview, which introduces the stack and the shells, and one level above the Technical Data Sheet, which states the specifications. Control narratives are in the Security Overview and are not repeated here.
Design principles
Six principles account for most of the structural decisions in the system. Each of them exists because the alternative depends on someone remembering to do the right thing.
- One codebase, every shell. Web, desktop, and mobile are shells over the same deployed application, not separate ports of it. A behavior or control exists once, so it cannot be present on one platform and missing on another, and there is no stale client left un-patched after a fix ships.
- The API is the single authorization chokepoint. The user interface is a client of the same REST API that third parties use. There is no privileged internal channel that bypasses a check the public API performs, so authorization has one place to be correct rather than two places to drift apart.
- Fail closed for anything automatic or credit-spending. Capabilities that run on their own or consume AI credits are opt-in and default to off, and a missing or unreadable setting is coerced to off rather than on. A workspace that has never enabled a feature sees a system that behaves as if the feature did not exist — the capability is withheld, not merely unused.
- Tenant scoping is enforced mechanically. Every workspace-scoped table carries a workspace identifier and every query must filter on it. This is checked by a linter in continuous integration that fails the build, not by reviewer discipline, because reviewer discipline is exactly what fails on the hundredth similar diff.
- Background jobs are idempotent. Scheduled work is designed so that a re-run, a duplicate invocation, or a retry after a partial failure converges on the same end state. Delivery decisions are made from stored due-times rather than from the exact minute a scheduler happened to fire.
- The audit trail is append-only. Security-relevant actions are recorded to a log whose immutability is enforced by the database itself, so the application — including any bug in it — cannot rewrite history.
Logical decomposition
The system divides into six layers. The boundary that matters most is between route handlers and data access: authorization is resolved before a query is written, and queries are never composed from unvalidated request input.
| Layer | Responsibility |
|---|---|
| Presentation | React Server and Client Components rendering the application UI. Server Components read data through the same server-side access paths as the API; Client Components hold interaction state and call the REST API. No authorization decision is made here — the presentation layer hides what a user cannot do, but hiding is never the control. |
| Route handlers | The HTTP boundary. Each handler terminates a request, authenticates it, resolves the acting identity and workspace, validates and coerces input to expected types, applies role and permission checks, and shapes the response. This is where authorization lives. |
| Domain & data access | Modules that own reads and writes for a functional area (tasks, projects, clients, planning, reporting, audit). They issue parameterized SQL scoped by workspace and encapsulate the invariants of their area, so business rules are not re-implemented per caller. |
| AI agent runtime | The governed tool-calling loop: model invocation, tool selection, policy evaluation, execution, ledger recording, and undo capture. Every agent loop in the product funnels through one execution chokepoint (see below). |
| Background jobs | Scheduled work — syncs, digests, reports, deliveries, maintenance — running as idempotent jobs enumerated in a health registry with expected intervals, so a job that stops running is detected rather than silently absent. |
| Integration adapters | Per-provider clients that translate between Kavanah's domain model and a third-party API, holding the provider's authentication mechanism, scope requirements, pagination, and error semantics. Credentials are decrypted only inside the adapter, at use. |
Request lifecycle
Every authenticated request — whether it originates in the web UI, a native shell, an API client, or an AI agent tool — passes through the same ordered sequence. The order is load-bearing: each step assumes the previous one succeeded, and no step is optional for a particular caller.
- TLS termination. The request arrives over TLS 1.2+ at the edge. HSTS is enforced on the production domain, so a downgrade to plaintext is refused by the browser before a request is made.
- Routing middleware. Requests to protected path prefixes without a valid session are redirected to sign-in before any handler runs. State-changing requests are subject to strict origin and CSRF validation, so a cross-site page cannot drive the API with a victim's cookies. Cache directives are set per surface here as well — authentication and onboarding responses are never stored.
- Route handler entry. The matched handler takes over. Request bodies and parameters are parsed and coerced to expected types at this boundary; nothing downstream trusts a raw request value.
- Authentication. The caller is identified either from a signed-JWT session validated server-side, or from a workspace API key presented as a bearer credential. Neither is trusted from a prior request: validation happens on every call.
- Workspace resolution and authorization. The target workspace is resolved and the caller's membership in it is confirmed, then the role (owner, admin, member, client-portal) and any granular permission scopes are checked against what the operation requires. An API key cannot exceed the RBAC of the user who minted it, and an AI persona cannot exceed its capability scopes. Failure here returns a denial before any data is read.
- Data access.The handler calls a domain module, which issues parameterized SQL filtered by the resolved workspace identifier. Identifiers supplied by the caller are always paired with the tenant filter, so a reference to another tenant's row simply matches nothing rather than returning it.
- Side effects and audit. Writes record their security-relevant effects to the append-only audit log, and agent-initiated writes additionally record a ledger entry and, where applicable, an undo entry.
- Response. A JSON response is shaped for the caller, exposing only fields the resolved identity is entitled to see. Errors are returned without leaking internal identifiers, query text, or stack detail.
Data model design
Tenancy root
The workspaceis the root of the tenancy model. Users, projects, tasks, clients, messages, integrations, agent configuration, and audit records all hang beneath a workspace, and a user's access is expressed as membership in one with a role attached. Cross-workspace relationships are not modelled implicitly; a user who belongs to two workspaces has two independent memberships with independent roles.
Row-level tenant scoping
Every workspace-scoped table carries a workspace_idcolumn, and every query against such a table must filter on it. This is verified by a linter that reads the query text at each call site and fails the build when the filter is absent; an exception requires an explicit allow-list entry with a written justification. The check is textual by design — hiding the tenant predicate inside a shared constant would defeat the tool that exists to catch cross-tenant leaks, so the filter is spelled out at every caller even when the rest of the predicate is shared. This control has already found and closed real cross-tenant defects before release.
Query construction
Data access uses parameterized SQL against managed Postgres. There is no ORM generating dynamic SQL from application objects, which means the query a reviewer reads is the query that runs, and values from a request are bound as parameters rather than interpolated into statement text.
Schema evolution
Schema changes are migrations registered in one central place, so the set of migrations that constitute the schema is enumerable rather than inferred from a directory listing. Every migration must be idempotent — expressed so that applying it to an already-migrated database is a no-op. That is not an aspiration: continuous integration applies the full migration set twice against a clean database and fails if the second pass errors, which is what makes a re-run during a partial deployment safe.
Sensitive fields
Integration credentials, OAuth tokens, MFA secrets, and classified PII are encrypted at the application layer with AES-256-GCM before they reach the database, under a subkey derived via HKDF-SHA256 from a root key and the owning workspace or subject. The design consequence is structural: a ciphertext read outside its own tenant context cannot be decrypted, so a hypothetical scoping defect on such a field yields unreadable bytes rather than a disclosure.
AI agent runtime design
Kavanah embeds an AI agent built on Anthropic Claude models driving a registry of roughly 896 tools. The design problem is not capability — it is making governance impossible to bypass as that registry grows. The answer is a single execution chokepoint.
The single chokepoint
Every agent loop in the product — interactive chat, scheduled runs, personas, and the other automated paths — executes tools through one execution function. Nothing calls a tool handler directly. That single seam is what makes the governance layers below unbypassable rather than merely present: adding a new loop that skipped it would silently escape the charter gate, the action ledger, and the policy engine at once, so the chokepoint is treated as a hard architectural invariant.
Tool registration and classification
A tool is not usable until it is registered, and registration is deliberately multi-part: the tool definition, its handler, its capability group, and — for anything that writes — its risk classification and its policy category. The registries are flat name maps, so a duplicate name would shadow rather than collide loudly; tests pin those maps to guarantee no shadowing exists.
| Classification | Design effect |
|---|---|
| Read-only | Explicitly listed as read-only. Available in restricted modes because it cannot change state. A tool that looks like a read but writes as a side effect is deliberately excluded from this list. |
| Write / risky | Registered as an explicitly risky tool, which is what causes it to be stripped from safe-mode runs. Absence from that registration is the failure mode the classification exists to prevent, so it is covered by tests rather than review. |
| Policy-categorised | Assigned an action category (for example communication, spend, or deletion) that the action-policy engine uses to decide whether the action runs, parks for approval, or is refused. |
| Task-creating | Additionally covered by a content gate applied at the agent boundary. Coverage is the feature: gating one task-creating tool but not its bulk variant would not be a gate at all. |
A test suite pins the invariant that the risky-tool registration and the safe-mode block list agree exactly, so the two cannot drift apart as tools are added.
Governance layers at execution time
- Capability scoping.A persona — the configurable AI identity, with its own instructions, model, and member access list — carries capability scopes that determine which tool groups it may use at all. Tools outside scope are not merely refused; they are not presented to the model, so an out-of-scope capability is never a temptation.
- Safe mode. In restricted autonomy, write tools are stripped from the toolset before the model is invoked. The agent cannot attempt an action it cannot see.
- Policy evaluation. In full autonomy, the action-policy engine evaluates the categorised action against workspace policy and either allows it, refuses it, or parks it for human approval — the action is held, surfaced to a person, and only executed if approved. Direct tool execution over HTTP is fail-closed here: where interactive chat may prompt, the programmatic path parks instead of running.
- Ledger recording.Every attempted and executed action is written to an action ledger, so the question “what did the agent do, when, on whose behalf, and under what policy decision” is answerable after the fact and not reconstructed from model transcripts.
- Undo capture. Where an action is reversible, an inverse operation is recorded to an undo stack at execution time. Inverses are registered per tool in a named registry, and reversal replays the recorded inverse rather than guessing at a compensating action.
Why the design is shaped this way
Each layer answers a different failure. Scoping answers “this identity should never touch that area.” Safe mode answers “this run should not change anything.” The policy engine answers “this specific action needs a human.” The ledger answers “what happened.” The undo stack answers “put it back.” None of them substitutes for another, and all of them hang off the same chokepoint so that a new agent surface inherits all five by construction. The data-use posture of the underlying AI providers, including the no-training position, is documented on the AI Transparency page.
Extensibility
Kavanah is extended through three deliberately narrow surfaces, all of which re-enter the system through the same authorization boundary as the product itself.
| Surface | Design |
|---|---|
| Community apps platform | Installable apps that a workspace opts into. An installed app can expose agent tools under a reserved namespace prefix, so a platform-provided capability is always distinguishable from a first-party one at the point of use, and the app's tools are subject to the same registration, classification, and policy machinery as built-in tools. |
| REST API | The same OpenAPI 3.1 API the product itself consumes, authenticated with a workspace API key that inherits the minting user's RBAC. An integration built against it cannot reach anything its owner could not reach in the UI. |
| Webhooks | Outbound delivery of audit-log events to a customer-controlled endpoint, letting customer security tooling observe activity without polling; inbound receivers accept events from supported integration providers. |
The design constraint common to all three: an extension is a client of the platform, never a peer of it. Extensions do not gain a privileged data path, and removing an extension removes its access.
Quality gates
The invariants described above are only real if a change that violates them cannot merge. Continuous integration runs on every change, and the gates are chosen to catch the specific classes of defect this system is prone to.
| Gate | What it protects |
|---|---|
| Type checking | Interface drift between layers — a handler and the domain module it calls cannot disagree about a shape. |
| Unit tests | Behavioral invariants, including the registration invariants for agent tools and the pinned no-shadowing guarantees for the tool registries. |
| Lint | Consistency and a zero-error baseline, enforced pre-commit as well as in CI. |
| SQL lint | Query hygiene across the data-access layer. |
| Tenant-scoping lint | The cross-tenant isolation invariant: a query against a workspace-scoped table missing its workspace filter fails the build, and any exception must be allow-listed with a written reason. |
| Migration idempotency | Applies the full migration set twice against a clean database, so a re-applied or partially applied migration cannot break a deployment. |
| API specification check | That documented routes and implemented routes stay in agreement, so the published OpenAPI specification is not a stale description of the system. |
| End-to-end browser suite | That the real authentication flow, redirects, and primary surfaces work against a real database — not against mocks. |
| Peer review | Every change is version-controlled and reviewed before merge; production access is least-privilege and scoped to the engineers who require it. |
Design decisions & trade-offs
The decisions below were made deliberately and cost something. The rationale column states what was bought and, where relevant, what was given up.
| Decision | Rationale and trade-off |
|---|---|
| Serverless functions rather than long-lived application hosts | Each request runs on an isolated instance, so there is no shared in-memory state between tenants and no application host for us to patch or for an attacker to persist on. Auto-scaling is inherent. The trade-off is that we cannot rely on process-local caches or in-memory sessions, which forces state to be explicit — a discipline that also makes tenant boundaries easier to reason about. |
| Single logical database with enforced row scoping, rather than a database per tenant | Operational simplicity — one schema to migrate, one backup and point-in-time-recovery configuration to exercise, one place to apply a fix — combined with a mechanical isolation guarantee, because the scoping rule is checked by a build-failing linter rather than trusted to review. A database per tenant would isolate by construction but multiply migration and restore risk across every tenant; we chose to make the isolation rule machine-checkable instead. |
| One codebase for three shells, loading the deployed app | Controls, permission checks, and fixes exist once and apply everywhere at the moment they ship; there is no stale client version in the field and no per-platform reimplementation to fall behind. The trade-off is that the shells depend on connectivity for the freshest experience, which is why cache behavior is tuned per surface rather than globally. |
| Per-workspace derived encryption subkeys for sensitive fields | A field cannot be decrypted outside its own tenant context, so key separation reinforces the scoping rule instead of duplicating it: even a hypothetical scoping defect on an encrypted field yields unreadable bytes. The cost is that key derivation is on the path of every sensitive read and write, and that recovery procedures must account for the derivation — a cost we accept for the isolation property. |
| Parameterized SQL rather than an ORM | The query a reviewer reads is the query that runs, which is what makes the tenant-scoping linter possible at all — a textual check cannot inspect SQL an ORM will generate later. The cost is more hand-written data access; the benefit is that isolation is verifiable by a machine. |
| A single agent execution chokepoint | Governance is attached to one seam, so every current and future agent surface inherits the charter gate, the policy engine, the ledger, and undo by construction rather than by remembering. The cost is that the chokepoint is a hard constraint on new agent work — which is the point. |
| Fail-closed defaults for automatic and credit-spending features | A workspace that has not opted in sees a system that behaves as though the feature does not exist, so nothing runs and nothing is billed on the strength of a default. The cost is that enabling a capability is an explicit act; we prefer that to a customer discovering an automated behavior they did not ask for. |
| Idempotent background jobs driven by stored due-times | Retries, duplicate invocations, and dropped scheduler ticks converge on the same end state instead of double-sending or silently skipping. The cost is that job design is more careful than a plain cron handler would need to be. |
| Database-enforced append-only audit log | Immutability is enforced beneath the application, so an application defect cannot rewrite the record of what happened. The cost is that corrections are additive entries rather than edits, which is exactly what an audit reviewer wants. |
Request a technical deep-dive
Due-diligence reviewers who need more than this document — a walkthrough of the agent governance model, the tenant-isolation enforcement chain, or our secure-development process — can request a working session with our engineering team.
Audit artifacts are handled separately: the SOC 2 report (currently in its observation period) and the annual penetration-test summary are shared under NDA through the Trust & Compliance page.
Questions from a security, privacy, or procurement team? Email security@kavanah.ai. For SOC 2, penetration-test, or questionnaire artifacts shared under NDA, use the request forms on the Trust & Compliance page.