Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/docs/content/docs/platform/self-hosting/redis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ Sim uses Redis as a message bus and shared cache. Both deployments ship it by de
With more than one app or realtime replica and no `REDIS_URL`, users on different pods stop seeing each other's edits and live status updates. Beyond one startup log line noting single-pod mode, nothing is logged — the app looks healthy and quietly loses events. Treat Redis as mandatory the moment `replicaCount` exceeds 1.
</Callout>

Everything Sim keeps in Redis is cache, coordination state, or an in-flight event — never committed data, which lives in PostgreSQL and object storage. Persistence is therefore not required. Losing or restarting the instance is not free, though: cancellation markers and the cross-pod half of execution streaming live here, so active runs stop streaming and a cancellation issued across the gap may not land. Completed work is unaffected.
Everything Sim keeps in Redis is cache, coordination state, or an in-flight event — never committed data, which lives in PostgreSQL and object storage. Persistence is therefore not required. Losing or restarting the instance is not free, though: cancellation markers and the cross-pod half of execution streaming live here, so active runs stop streaming and a cancellation issued across the gap may not land.

One exception is worth planning for. High-volume webhook triggers claim their idempotency keys in Redis by default, trading a narrow durability window for the throughput. A restart drops those claims, so a provider that retries a webhook Sim already finished can have it execute a second time. Flows where a repeat is not acceptable — anything touching money, billing, or compliance — should not rest on that claim. `forceStorage: 'database'` on the idempotency service keeps the claim in PostgreSQL so a restart cannot drop it, but it still claims, runs, and records the result in separate steps, so a side effect that commits before the result is recorded can replay. Where the side effect itself must never run twice, `executeTransactionallyIdempotent` commits the claim, the mutation, and the result together in the caller's transaction. Enabling Redis persistence narrows the window but does not close it.

## Configuration

Expand Down
2 changes: 1 addition & 1 deletion apps/sim/app/api/knowledge/member-connectors/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const GET = defineInternalJsonRoute({
auth: internalSessionAuth,
operation: knowledgeOperations.listWorkspaceMemberConnectors,
rateLimit: internalRateLimits.none({ reason: 'One small read per visit to the Search tab' }),
errorPolicy: internalKnowledgeErrorPolicies.connectors,
errorPolicy: internalKnowledgeErrorPolicies.memberConnectors,
mapInput: ({ query }) => ({ workspaceId: query.workspaceId }),
useCase: listWorkspaceMemberConnectors,
present: ({ connectors }) => ({ success: true as const, data: connectors }),
Expand Down
17 changes: 10 additions & 7 deletions apps/sim/app/workspace/[workspaceId]/home/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -543,23 +543,26 @@ export function Home({ chatId, userName, userId }: HomeProps) {
]
)

/** An emptied search box returns to the sources; a send in any other mode has no search to clear. */
const clearSearch = useCallback(() => {
if (searchQueryValue !== null) setSearchQuery('')
}, [searchQueryValue, setSearchQuery])

/**
* A queued message re-enters the composer in the mode it was written in: an
* Assistant question edits as an Assistant question, and never as a Search,
* which submits nothing and would leave the edit stranded.
* which submits nothing and would leave the edit stranded. Leaving Search
* drops the query as it does everywhere else; a live one would put the
* composer straight back into Search and strand the edit anyway.
*/
const restoreQueuedMode = useCallback(
(requestMode: QueuedMessage['requestMode']) => {
clearSearch()
void setComposerMode(requestMode === 'ask' ? 'assistant' : 'build')
},
[setComposerMode]
[clearSearch, setComposerMode]
)

/** An emptied search box returns to the sources; a send in any other mode has no search to clear. */
const clearSearch = useCallback(() => {
if (searchQueryValue !== null) setSearchQuery('')
}, [searchQueryValue, setSearchQuery])

/**
* Summarize or Answer on a result: switch to Assistant and hand the question
* to it. The submit reads the mode from this render, so it is sent as an
Expand Down
23 changes: 22 additions & 1 deletion apps/sim/lib/knowledge/api/route-policies.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import {
WorkspaceApiKeyScopeAuthorizationError,
} from '@/lib/core/application'
import { OrchestrationError } from '@/lib/core/orchestration/types'
import { v2KnowledgeErrorPolicies } from '@/lib/knowledge/api/route-policies'
import {
internalKnowledgeErrorPolicies,
v2KnowledgeErrorPolicies,
} from '@/lib/knowledge/api/route-policies'

describe('v2 knowledge error policies', () => {
it.each([
Expand Down Expand Up @@ -70,3 +73,21 @@ describe('v2 knowledge error policies', () => {
})
})
})

describe('internal knowledge error policies', () => {
it('conceals a knowledge-base-scoped connector authorization failure', () => {
const projected = internalKnowledgeErrorPolicies.connectors.project(
new NoWorkspaceAccessError()
)
expect(projected?.status).toBe(404)
expect(projected?.body).toMatchObject({ error: 'Knowledge base not found' })
})

it('does not conceal the workspace-scoped member-connector listing', () => {
const projected = internalKnowledgeErrorPolicies.memberConnectors.project(
new NoWorkspaceAccessError()
)
expect(projected?.status).not.toBe(404)
expect(JSON.stringify(projected?.body)).not.toContain('Knowledge base not found')
})
})
2 changes: 2 additions & 0 deletions apps/sim/lib/knowledge/api/route-policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@ export const internalKnowledgeErrorPolicies = {
internalKnowledgeErrorPolicy('Failed to process knowledge tag request')
),
connectors: concealKnowledgeBase(internalKnowledgeErrorPolicy('Internal server error')),
/** Workspace-scoped like the bulk routes above, so likewise not concealed. */
memberConnectors: internalKnowledgeErrorPolicy('Failed to fetch member connectors'),
uploads: concealKnowledgeBase(internalKnowledgeUploadErrorPolicy),
} as const

Expand Down
Loading