{
  "markdown": "# assistant-platform\n\nThree distributions and the wire between them. Nothing here knows about genes,\nstrategies or VEuPathDB: an assistant built on this runtime brings its own\nscience.\n\n| folder | distribution | import name |\n| --- | --- | --- |\n| `packages/assistant-core/` | `assistant-core` | `assistant_core` |\n| `packages/assistant-client-ts/` | `@veupathdb/assistant-client` | - |\n| `packages/mcp-conformance/` | `veupathdb-mcp-conformance` | `mcp_conformance` |\n\n## The `screening` extra\n\n`assistant-core[screening]` adds the ONNX runtime and the tokenizer that\n`assistant_core.capabilities.input_screening` needs. A host that screens user\ntext before an agent reads it declares the extra and tells the scanner which\ndirectory holds the model; an assistant that screens nothing declares plain\n`assistant-core` and carries neither wheel.\n\n## One Yarn project, one lock\n\nThis folder is its own Yarn project: `package.json` declares\n`packages/assistant-client-ts` as its only workspace, pins the Yarn release, and\n`yarn.lock` here is what the client resolves against. `yarn install --immutable`\nis the first step of the client's CI lane, so the suite runs against the versions\nthe lock names rather than whatever a fresh install picks.\n\nA consuming application names this repository, the workspace and one release tag\n(`\"@veupathdb/assistant-client\":\n\"git+https://github.com/VEuPathDB/ai-assistant-platform.git#workspace=@veupathdb/assistant-client&tag=v<version>\"`).\nYarn clones the repository, installs it with its own lock, runs `prepack` and\npacks `dist`, so the consumer compiles the built output and needs no install\nhere.\n\nFrom `ai` 6.0.250 a resumed stream is seeded from empty state instead of from\nthe assistant message the client already holds, so the client rebuilds the\nmessage the tail continues. `DurableChatTransport` therefore resumes a message\na turn left open from a cursor before that message's own `start`, drops what the\ntail delivers before it, and opens the next tail itself where the host ended one\nat a `done`. That is what section 6.1 of `PROTOCOL.md` asks of a turn suspended\non a durable task: the gap's `data-task-progress` and `data-task-completed`\nchunks belong to the suspended turn's message, and a host serves them on a\nsecond tail. The cursor a replay names comes from the store, which the snapshot\nseeds from its `openMessage`, so a reload never tails from `0`. The peer range\nis `>=6.0.250 <8`, the releases that seed a resume that way;\n`tests/conformance/replayedMessage.test.ts` is the gate, and it fails on\n`ai` 6.0.154.\n\n## The runtime carries its own migration chain\n\n`assistant-core` owns `conversations`, `messages`, `conversation_events`,\n`memory_tombstones`, `chat_turn_cancellations`, `monthly_usage`,\n`scratchpad_notes` and `scratchpad_compactions`, and ships\nthe alembic history that creates them under `src/assistant_core/alembic/`,\nrecording its position in `alembic_version_assistant_core`. A host\napplication's chain uses its own version table, so the two share a database\nwithout touching each other.\n\n```bash\nuv run python -m assistant_core.migrate     # bring the runtime's tables to head\n```\n\nThe runtime does not migrate at start. A host that embeds this package as a\nlibrary runs `assistant_core.migrate.upgrade_head(connection)` on its own\nconnection, after its own chain, because the runtime's tables name host tables\nin foreign keys. A database whose host chain already created the tables a\nrevision would build needs no `alembic stamp`: each revision asks the inspector\nfirst and records the position without rebuilding anything. A database holding\nsome of one revision's tables is refused, naming which are present and which\nare missing.\n\n`assistant_core.migrate.OWNED_TABLES` is the ten names, and\n`assistant_core.migrate.include_object` is the alembic filter that keeps them. A\nhost whose own `env.py` maps its tables on the same declarative base uses that\nfilter's complement, so neither chain autogenerates a revision for the other's\ntables. A revision names the tables it created in its own `CREATES`, which does\nnot move when the distribution grows; a test holds the union of those to\n`OWNED_TABLES`.\n\nThe runtime declares one host table it does not own. A host supplies `users`\nwith a uuid `id`. Nothing else is read from it.\n\n## What a host supplies to the runtime\n\nThe runtime holds the rules that read its own rows and hands back the decisions\na product makes. These seams carry that split.\n\n`assistant_core.quota` counts spend into `monthly_usage` per user per\napplication, and `get_current(session, user_id, limit_usd=...)` takes the\nbudget as an argument: the runtime stores no limit and reads no user record.\nWhat a caller at a hundred percent is told is the host's.\n\n`assistant_core.conversation.cancellation` writes the stop row a running worker\npolls, and fails the job of a worker that is already gone through\n`assistant_core.tasks.maintenance`.\n\n`assistant_core.conversation.authz` answers ownership over a\n`ConversationLookup`, a protocol whose one member is `get_by_id`. A host passes\nthe thread store it already holds and inherits nothing from the runtime.\n\n`assistant_core.scratchpad.rendering.render_scratchpad` draws a thread's note\nindex and appends a `ScratchpadGuidance`, three strings the host writes: what to\nstart noting on an empty scratchpad, what to do before the turn ends on a filled\none, and what is worth promoting to long-term memory.\n`build_scratchpad_toolset(guidance=...)` is a value a host puts in an agent's\n`toolsets`; the runtime names no agent, and the third string lands on the\n`promote_to_memory` tool description, which is what the model reads when it\ndecides to promote.\n\n`assistant_core.scratchpad.compactor.compact_scratchpad` takes a factory that\nbuilds the compactor agent, so the model and the rewriting instructions are the\nhost's, and a host builds one only when a ceiling is passed. The runtime owns\nthe gate, the token trim, the cost and the write-back.\n\n`assistant_core.tasks` runs durable tools on a queue the host opens.\n`install_task_app(app)` gives the runtime the procrastinate application and its\nschema; `install_worker_context(build)` builds the turn context a durable body\nreads; `install_completion_turn(run)` drives the turn a finished task opens;\n`install_durable_job_context(ctx)` carries state a worker cannot re-derive, so\nthe runtime names no product's credential. Each of those has a `reset_*` in the\nsame module. A host subclasses `DurableJobState` and types every credential\n`CarriedSecret`, and `register_durable_jobs` scrubs the carried state out of\nthe queue's own log lines, so no host filter matches on a product's key name.\n`declare_durable_tool` names a tool once, and the decorator, the procrastinate\njob and the worker body all read that value.\n`assistant_core.tasks.heartbeat.HeartbeatThread` writes the beat that\n`worker_dead_heartbeat_seconds` reads, and the settings refuse a beat too slow\nfor that window. `assistant_core.tasks.names` holds the queues and the job\nnames a host wires, and `assistant_core.tasks.chat_turn` states the two fields\nthe stalled-job sweep reads out of a host's chat-turn payload.\n\n`assistant_core.registry.install_assistant_registry` serves the assistants to\nwork that carries no request, such as the turn a finished durable task opens.\n\n`assistant_core.errors.AssistantCoreError` is the base of every refusal the\nruntime raises across that surface (`ConversationNotFoundError`,\n`ConversationForbiddenError`, `TurnStillRunningError`). None of them names an\nHTTP status; a host maps them onto its own transport.\n\n## PROTOCOL.md is the contract\n\n[`PROTOCOL.md`](packages/assistant-core/src/assistant_core/PROTOCOL.md) is the\nwire an `assistant-core` deployment serves and the TypeScript client reads: the frame grammar, cursor semantics, the\nsnapshot and tail contract, the turn shape, the chunk vocabulary and the\nreduction rules. It is versioned and additive only.\n\nBoth sides are pinned to it. `assistant-core`'s\n`tests/integration/conversation/test_protocol_document.py` compares the document\nagainst the chunks the runtime actually emits, so a new chunk kind fails there.\nThe client's suite is the **consumer-side gate**: `yarn sync:protocol` reads the\ndocument into `src/protocol/captured.json`, and `tests/conformance/` fails when\nthe capture and the document disagree. The sync is an authoring step; the gate\nis the suite that reads the capture back. A change to `PROTOCOL.md` that neither\nside implements fails both.\n\nThe document ships inside the runtime package, so an installed consumer reads it\nat `Path(assistant_core.__file__).parent / \"PROTOCOL.md\"`, the same bytes the\ndeployment serves. `tests/packaging` builds the wheel and reads it back.\n\n## mcp-conformance is an admission gate\n\n`mcp_conformance` is the suite an MCP tool server passes before a deployment\nadmits it. It runs against a served endpoint\n(`pytest --pyargs mcp_conformance --mcp-endpoint <url> --mcp-bearer <token>`)\nand produces an admission record. It ships apart from the runtime because a\ndeployment reads a server it did not build.\n\n## Gates\n\n```bash\nyarn install --immutable\ncd packages/assistant-core        && uv sync --frozen && uv run ruff check src tests && uv run ruff format --check src tests && uv run mypy --strict src && uv run pytest && uv run pytest tests/packaging -m wheel --override-ini addopts=''\ncd packages/assistant-client-ts   && yarn typecheck && yarn lint && yarn format:check && yarn test && yarn build\ncd packages/mcp-conformance       && uv sync --frozen && uv run ruff check src tests && uv run mypy --strict src && uv run pytest\nnode scripts/check-knowledge.mjs  && node --test scripts/check-knowledge.test.mjs\n```\n\nThose four lanes are what `.github/workflows/ci.yml` runs, command for command.\n`.pre-commit-config.yaml` carries the same checks as hooks, and leaves the wheel\ncheck and the integration half of the runtime suite to CI.\n\n`assistant-core`'s suite runs with **no** application installed; that is what\nmakes the boundary an installation fact rather than a lint rule.\n\n## docs/knowledge is the durable record\n\n[`docs/knowledge/`](docs/knowledge/index.md) holds the choices behind this\nrepository, in Open Knowledge Format v0.2. `scripts/check-knowledge.mjs` is its\ngate: every page carries a `type`, every relative link resolves, and every page\nis linked from its directory's index.\n",
  "bytes": 10399,
  "sha": "d163fa1ae534bf11d725804aeefd6271551d528c6fce6b71fa7588da78eb6b96",
  "repo_slug": "veupathdb/ai-assistant-platform",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_veupathdb_ai_assistant_platform_docs_kno_09ef903a/readme"
}