Skip to content
Merged
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
23 changes: 9 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,22 +36,17 @@ already refused the same way.

Sessions survive and nobody signs in again.

### Changed

- **This deployment does not search documents itself.** A Bot answers from a live system by calling
that system's own search as the person asking, so the vendor decides what they may see and there is
no second copy of anybody's documents here to keep in step, to secure, or to leave behind when
somebody is removed. The local index that was being filled — `documents`, `chunks` and
`document_acls` — is read by nothing, and the connector that filled it is going away. Retrieval over
a copy of a customer's corpus is not a thing OpenBot does.

### Added

- **A Bot can answer from a connected source, as the person asking.** The connectors have been
writing `documents`, `chunks` and `document_acls` and nothing ever read them back, so a deployment
that connected a source got rows in PostgreSQL and still no citation. A Bot now has a
`search_company_knowledge` tool, and it returns only the documents the person asking is allowed to
read — filtered in the database against that person's own principals rather than fetched and
filtered in the server, so a document they may not read is never handed over. A deny beats an
allow, and a document with no ACL rows is readable by nobody rather than by everybody. Each result
carries the document's title, the link that opens it, and the passage that matched. A search that
finds nothing says so, rather than returning an empty string a model would fill in from memory.
Every search is on the audit trail as `knowledge.searched`, naming the query and the documents
returned and never quoting their text. The tool is only offered when there is something to search.
Matching is PostgreSQL's own full-text search over the stored passages: nothing in the deployment
produces embeddings yet, so the vector column is left alone and ranking by meaning follows the
first connector that writes one.
- **Releases are cut by a workflow, not by hand.** `Create release PR` bumps the version and promotes
`## Unreleased` to a numbered section; merging the pull request it opens is what publishes. Merging
builds and pushes one image to `ghcr.io/copilotkit/openbot`, signs a build provenance attestation
Expand Down
38 changes: 2 additions & 36 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ import {
resolveModelApiKey,
} from "./credentials";
import { createDatabase } from "./db/client";
import { askerFor, createKnowledgeSearch } from "./knowledge/search";
import { knowledgeSearchTool } from "./knowledge/tool";
import { createPeopleStore } from "./people/store";
import { createPluginStore } from "./plugins/store";
import { grantedTools } from "./plugins/tools";
Expand Down Expand Up @@ -266,15 +264,6 @@ const pluginStore = createPluginStore({
policy: () => policyStore.get(),
});

/**
* Reading back what the connectors wrote.
*
* `connectors/sync-persistence.ts` has been filling `documents`, `chunks` and `document_acls`, and
* nothing has ever read them. This is the read half, and it filters on the asker's own principals in
* SQL rather than here. See server/src/knowledge/search.ts.
*/
const knowledgeSearch = createKnowledgeSearch(database);

void recordAuditEvent(bootAuditStore, {
eventType: "computer.policy_loaded",
targetType: "policy",
Expand Down Expand Up @@ -399,31 +388,8 @@ const app = createApp(
stallGuard,
// Tools run here, not in the browser. Each one still executes through the plugin store, so the
// grant, the policy and the audit row are exactly where they were.
//
// The knowledge search is beside them rather than inside the plugin store, because it has no
// vendor to reach: it is a query against this deployment's own tables, and it writes its own
// `knowledge.searched` row. It is offered without a per-Bot grant because the ACL filter in the
// query is the access control — the search runs on the asker's principals, so no Bot can return a
// document the person asking could not open themselves. Offered only when there is something to
// search, so a deployment that has connected nothing does not describe a tool that can only
// answer "nothing found".
(actorId) => async (botId) => {
const granted = await grantedTools({
store: pluginStore,
botId,
actorId,
});
if (!(await knowledgeSearch.anyDocuments())) return granted;
return [
...granted,
knowledgeSearchTool({
search: knowledgeSearch,
auditStore: bootAuditStore,
asker: await askerFor(database, actorId),
botId,
}),
];
},
(actorId) => (botId) =>
grantedTools({ store: pluginStore, botId, actorId }),
/*
* What the deployment tells a remote Bot about the run it is starting.
*
Expand Down
232 changes: 0 additions & 232 deletions server/src/knowledge/search.ts

This file was deleted.

Loading