Skip to content
bitzorcas
中EN

Reference

AIManage workspaces, providers, and models

Workspace aggregates, encrypted provider credentials, single-default orchestration, model metadata, tenant ownership, cache invalidation, and production configuration boundaries.

Last updated

A workspace is the tenant configuration boundary for name, active state, default model, and system prompt. Providers are asymmetric credential child records. AIModel is a separate unified aggregate. All three persist, but only workspace and default-provider data currently affect chat execution.

1. Data shape

TenantId + WorkspaceIdTenantId + ProviderId

AIWorkspace
Id · TenantId · Name · IsActive
DefaultModelId · SystemPrompt

AIProviderCredentialRecord
Id · TenantId · WorkspaceId
Endpoint · EncryptedApiKey · IsDefault

AIModel
Id · TenantId · ProviderId
ModelIdentifier · MaxTokens · Temperature

Provider and Message use *Record because they are genuine asymmetric child tables, not legacy one-to-one entity mirrors. Infrastructure depends on IEntitySet<T>, with generated metadata projected to both supported ORMs.

2. Create a workspace

Create a tenant AI workspace
var result = await mediator.Send(new ManageWorkspace.CreateCommand(
Name: "Support knowledge assistant",
Description: "Answers only from approved material",
DefaultModelId: "gpt-4o-mini",
SystemPrompt: "Cite evidence; say when evidence is missing."), cancellationToken);
// TenantId comes from ICurrentUser; the persistence adapter assigns the final Id.
return result.Value;

The handler rejects only blank names. It does not prove tenant-unique name, prompt size/classification, or existence of DefaultModelId. Those boundaries need explicit error contracts.

3. Provider creation and the default invariant

Add a default provider
POST /api/ai/workspaces/ws-001/providers
Content-Type: application/json
{
"providerType": "OpenAICompatible",
"name": "production-east",
"endpoint": "https://ai-gateway.example.com/v1",
"apiKey": "<secret-from-vault>",
"isDefault": true
}

The store validates workspace ID plus current tenant, assigns an ID, clears other defaults, and writes the encrypted credential. It also handles pending providers inside one unit of work. The database metadata has a normal (TenantId, WorkspaceId) index, not a filtered unique constraint, so concurrent transactions can still create two defaults. GA needs a database-expressible invariant, serialized update, or workspace lock with stable conflict mapping.

4. Credential semantics

ApiKeyCipher uses ASP.NET Core Data Protection with purpose AIManage.ApiKey. Provider reads decrypt internally; public summaries omit the key.

Production Data Protection direction
// Deployment requirement example; this does not claim the host already configures shared keys.
services.AddDataProtection()
.SetApplicationName("BitzOrcas.Modern");

Decrypt currently catches every exception and returns the input for historical plaintext compatibility. Lost keys, corrupted ciphertext, or purpose mismatch can therefore be sent to the provider as an API key. Use a versioned ciphertext marker: migrate identifiable plaintext once; fail closed and alert when marked ciphertext cannot decrypt.

Production must persist/share the key ring across instances, protect it with a proper KMS, test rotation and restore, and prevent keys from entering logs, traces, snapshots, or dumps.

5. Persisted models do not control chat

SaveModelAsync inherits TenantId from the provider and stores identifier, display name, type, MaxTokens, and Temperature. The external model contract has no TenantId; provider lookup uses ProviderId only, making global provider-ID uniqueness an important implicit assumption.

Send/Stream never reads the model table. A caller can submit any model string and execution stays fixed at 4096/0.7.

GA target: resolve managed model policy
// Target contract, not current source.
var resolved = await modelPolicy.ResolveAsync(
tenantId, workspaceId, requestedModelId, cancellationToken);
// Clamp caller preferences to the persisted tenant model policy.
var options = new ChatOptions
{
ModelId = resolved.ModelIdentifier,
MaxOutputTokens = Math.Min(requestedMaxTokens, resolved.MaxTokens),
Temperature = resolved.Temperature
};

6. System-prompt inheritance

Conversation creation copies the workspace prompt only when its request prompt is null. Send later uses conversation prompt, otherwise workspace prompt. An explicit conversation prompt behaves as a snapshot; a null prompt dynamically follows workspace changes. The product contract should choose and record prompt version/hash for replay and audit.

7. Client cache and rotation

The client-cache key includes ProviderId, ModelId, and a truncated SHA-256 of endpoint/model/key; the entry also retains the raw key for collision checking. A key or endpoint change creates a new entry, while the old client remains until eviction/disposal.

Concurrent misses build outside the lock. A losing thread returns the existing client without disposing the unused client it just built. Add active invalidation, single-flight construction, disposal of losers, metrics, and an explicit assessment of raw credentials retained in process memory.

8. Review table

ConcernCurrent factGA requirement
workspace nameblank checklength and tenant uniqueness
endpointarbitrary stringHTTPS, allowlist, SSRF policy
credentialData Protectionkey lifecycle, version, fail closed
default providerapplication updateconcurrent uniqueness
modelarbitrary request stringmanaged allowlist and quota
optionsfixed 4096 / 0.7use model policy with bounds
promptraw stringsize, version, classification, audit

Existing tests cover store behavior and dual-ORM parity. Add concurrent default creation, cross-tenant provider ID, corrupted/rotated keys, endpoint SSRF, cache contention, model override denial, prompt versioning, and provider contract tests.

9. Operational troubleshooting

SymptomFirst evidence to inspectUnsafe shortcut to avoid
provider suddenly returns 401credential version, key-ring availability, provider rotation timelogging or returning the key
two defaults appearconcurrent writes and database constraint evidencechoosing an arbitrary first row forever
new model setting has no effectSend/Stream model resolution and fixed optionsassuming persisted metadata is enforced
clients grow after rotationcache entries by provider/model and eviction metricsrestarting as the only invalidation design
one node decrypts and another failsshared key ring and application namecopying plaintext credentials into config

10. Change-review questions

Before accepting a provider/model change, identify the tenant-bound lookup, endpoint egress policy, ciphertext version, rotation path, database concurrency invariant, cache invalidation signal, and provider contract evidence. A configuration UI does not establish any of those runtime guarantees.

Terminal window
# Keep credential, model, and default-provider implementation assumptions visible.
rg -n "SaveProviderAsync|SaveModelAsync|GetDefaultProviderAsync|return cipherKey" \
src/Platform/AIManage -g '*.cs'

AIManage overview · Conversation security · GA gate

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%