A run from an agent's API key or widget acts as the agent's owner, so it
could rewrite any wiki the owner can edit. A new per-wiki setting,
wiki_outside_edits (off by default), decides whether such runs, and runs
from the agent's public link, get the wiki's edit actions. While it is off
they are offered only wiki_view, and the tool refuses writes itself after
reading the live setting. The owner changes it through the owner-only
/api/sources/<id>/wiki/settings route; tokens can read it but not change it.
The backend import package is now docsgpt, the name it will carry on PyPI;
application was far too generic to install into anyone's site-packages.
git mv plus a mechanical rewrite of every import, dotted string and path
reference: 734 Python files, the compose files, Dockerfile, workflows, docs,
setup scripts, devcontainer, k8s manifests, vscode config, pytest and coverage
config, .gitignore. Behaviour is unchanged.
Kept for one release:
- A top-level application package whose meta-path finder resolves
application.x.y to the already-imported docsgpt.x.y object, so old imports
and entry points (celery -A application.app.celery,
uvicorn application.asgi:asgi_app) keep working with a FutureWarning.
- Celery registers every application.* task name as an alias of its
docsgpt.* task on start-up, so messages queued by the previous release still
run. The redbeat key prefix moves to redbeat:docsgpt:v2: so schedule entries
the previous release wrote are left unread instead of firing twice.
The backend image builds from the repository root (docker build -f
docsgpt/Dockerfile .) so it can ship the alias package; a root .dockerignore
allow-lists docsgpt/ and application/ and keeps caches, local data, .env
files, the sample index files and the Dockerfile out. Compose and the image
workflows point at the new context.
Five defects from a review of the embeddings work, four of them silent.
- Write local files atomically. `LocalStorage.save_file` streamed straight onto
the destination, so an interrupted write left a truncated file. `reembed`
rewrites every index it touches, and a half-written `index.faiss` loads at
neither the old width nor the new one -- the source was unrecoverable, with
no backup and no temp file left behind. Bytes now land beside the destination
and move into place with `os.replace`. S3 was already safe (single PUT).
- Read pgvector chunks a page at a time. `reembed_pgvector` materialised every
`(id, text)` row for a source before embedding -- ~1.6 GB at 200k chunks and
several times that for non-Latin scripts, with the `PGresult` held alongside
until the cursor closed. Inside the shipped 4Gi limit, while also holding the
model, that is an OOMKill -- which is exactly the SIGKILL the point above
turned into a destroyed index. It now walks the source by keyset.
- Bound the first wave of delegated embeds. The failure cooldown is only latched
once the first `get()` returns, so every request already in flight paid the
full EMBEDDINGS_DELEGATE_TIMEOUT: measured 64 threads all timing out together,
and at the shipped 60s across a 96-thread WSGI pool that is an API serving
nothing at all, health checks included. One caller now probes while the rest
fail fast; after a single success the gate leaves the path entirely.
- Ship EMBEDDINGS_NAME commented in .env-template. The comment directly above it
says to leave it commented when upgrading, and the line shipped set. Any value
reaching `.env` lands in `model_fields_set`, which makes `resolve_embeddings_pin`
bail -- so a template-derived `.env` disabled the legacy pin outright and
repointed a populated index at a different 768-dim model, where no width check
fires. The pin already picks granite for a fresh install and mpnet for an
existing one, so nothing needs to be set by hand.
- Stamp `sources.model` on wiki sources. They were created with the column NULL
and then embedded like any other source, and the boot check reads NULL as
"pre-dates the column, therefore the legacy model" -- reporting a correctly
embedded source as stale on every startup of every process. Stamped at
creation, and again on each page re-embed so existing rows heal.
The two docs that promised the FAISS index survives a failed run said so of the
embed only; both now describe the write, and upgrading.mdx says to stop ingest
for the duration.
sources.tokens was only set at ingest, so wikis showed no/stale token count on the card (blank wikis showed nothing; converted ones showed the stale original count). rebuild_wiki_directory_structure (called after every wiki mutation) now also sets sources.tokens to the sum of wiki_pages.token_count; blank wiki create sets tokens=0. So the card reflects live wiki content.
Unit 6 of F-Wiki (D22 + D24). Migration 0024 adds wiki_pages.updated_via; set to 'agent' by WikiTool + convert, 'human' by the edit/seed endpoints (content-hash short-circuit preserves it). WikiViewer gains a markdown editor (Save via PUT with expected_version; 409 reloads latest while keeping the draft; 403 graceful) and a per-page provenance stamp (editor/when/version). Edit gated on write access client-side; backend remains the real authz.
Unit 5 of F-Wiki (D20-D23). convert_source_to_wiki task reuses reingest's storage file-load + parser to materialize files->wiki_pages (one page/file), skips/reports non-text, re-embeds per page, and flips kind=wiki + exposure=agentic_tool only when pages were created. POST /wiki/convert (explicit, write-authz; blank source enables inline, fileful enqueues the task; rejects mid-ingest). PUT /wiki/page for human edits (write-authz, optimistic version -> 409, re-embed). PATCH /config preserves kind (kind changes only via convert).
Unit 4 of F-Wiki. POST /api/sources/wiki creates a type=wiki/kind=wiki source with no ingest task (optional seed page enqueues reembed). GET /wiki/pages + /wiki/page serve the tree + fresh page content, read-access gated (owner or team grant), path-validated. Frontend: 'Create Wiki' ingestor entry, read-only WikiViewer (FileTree + react-markdown, no raw HTML), sync/reingest hidden for wiki sources.