Tools System¶
Deterministic Python functions and Agno Toolkit classes perform the scientific
work. Language models select tools and interpret results; they do not replace
descriptor calculation, database queries, GTM fitting, model inference, or
retrosynthetic search.
Scientific toolkits¶
The main packages under src/cs_copilot/tools/ are:
tools/
├── databases/ ChEMBL REST and SQL backends
├── chemography/ GTM construction, projection, landscapes, and sampling
├── chemistry/ standardization, descriptors, similarity, design, retrosynthesis
├── analysis/ robustness result analysis
├── io/ DataFrame pointers, session memory, skills, and report export
└── constants.py shared tool configuration
Toolkit classes register selected methods with Agno. Method names, signatures, docstrings, and return values form part of the LLM-visible interface, so tool methods should remain deterministic, validate inputs, and return explicit artifact references where appropriate.
MCP exposure¶
MCP reuses the scientific implementations through an additional contract
layer under src/cs_copilot/mcp/:
- tool specifications define public names, schemas, risk annotations, and profile membership;
- facades adapt session and workflow context to toolkit calls;
- the tool adapter enforces profile, task, timeout, idempotency, and artifact rules; and
- the server publishes only the selected capability profile.
A toolkit method is not automatically public through MCP. It must have an MCP
specification whose name starts with its group's prefix (gtm_, chem_, ...)
and belong to the selected profile. Role grants and profiles live in one
capability table, src/cs_copilot/capabilities.py: changing a role's groups
changes both the Agno specialist's allowlist and the MCP roles of every tool in
those groups, and every role must still fit inside its assigned profile.
ChEMBL backends¶
ChemblToolkit supports REST, SQLite, PostgreSQL, and MySQL. With
backend="auto", configured local databases are tried before REST and a
missing optional SQL driver falls through to the next candidate. REST requires
no local database configuration.
Local SQL deployments are useful for high-volume or offline queries. Database
connection settings are documented in the installation guide and .env.example.
SynPlanner backend¶
The SynPlannerToolkit wrapper and the external SynPlanner/CGRtools stack are
installed with the default project dependencies. On first use, SynPlanner
downloads its runtime model and chemistry data when no local synplan_data
directory is available.
The molecular latent engine¶
AutoencoderToolkit encodes SMILES to a latent vector and decodes latent
vectors back to molecules. chembed, a SELFIES transformer VAE
(3BioCompBio/chembed-default, 227 MB), is the engine behind it. It is supplied
as an AutoencoderEngine object, so the toolkit class, its nine registered Agno
tools and the generation_engine="autoencoder" provenance string do not name a
model. Build the toolkit through make_autoencoder_toolkit().
The deepchemography LSTM autoencoder was retired after v0.4.0. It could not
encode SMILES longer than roughly 50 characters, which is most of the potent
chemotypes, and its 256-wide latent is unrelated to chembed's. Check out the
v0.4.0 tag to reproduce results recorded on it. Names that selected it —
AUTOENCODER_BACKEND=deepchemography, the robustness latent_engine key, and
the autoencoder, ae, latent and embedding descriptor families — now
raise rather than silently running on chembed, so no result is attributed to a
model that did not produce it.
deepchemography names a package and an engine, and they are not the same
thing. The package ships two unrelated models: LSTMAutoencoder, the retired
small-molecule engine, and peptides.PeptideWAE, which backs peptide design and
has no chembed counterpart. Retiring the engine therefore did not retire the
dependency, and peptide generation keeps needing it.
What chembed ignores¶
chembed decodes greedily. temperature, decode_mode and max_length are
accepted and have no effect, so an agent must not offer a temperature or
sampling sweep when this engine is active — get_model_info() reports
supports_temperature and supports_stochastic_decode as False. Diversity
comes only from latent noise (noise_scale, latent_std): a single encoded
molecule has a latent standard deviation near 0.72. Every generation audit
records the requested
settings alongside the applied ones, plus ignored_parameters, so no artifact
claims a setting that was never used.
Because diversity comes only from the noise, the scale matters. Measured over aspirin, lidocaine and a large peptidomimetic, 60 samples each:
noise_scale |
aspirin | lidocaine | large seed |
|---|---|---|---|
| 0.1 | 0 distinct | 0 distinct | 1 distinct |
| 0.25 | 1, Tanimoto 0.55 | 6, Tanimoto 0.69 | 39, Tanimoto 0.52 |
| 0.5 | 23, Tanimoto 0.55 | 35, Tanimoto 0.50 | 60, Tanimoto 0.25 |
| 1.0 | 57, Tanimoto 0.30 | 60, Tanimoto 0.21 | 60, Tanimoto 0.17 |
At 0.1 the decoder simply returns the parent. chembed therefore defaults to
0.5, which yields analogues across the size range. Larger seeds drift further at
any given scale, so analogue work on big molecules wants 0.25. Omitting
noise_scale uses the engine's own default rather than a fixed number.
Sampling is also slower, because the decoder is autoregressive: 5000 molecules
take roughly 35 s on a GPU and about 17 minutes on CPU. sample_molecules
therefore defaults to 5000 on CUDA and 500 on CPU when no count is given, and
records which default it used.
A manifold belongs to one latent space¶
The retired LSTM also emitted 256-wide latents, so a manifold fitted on it accepts chembed vectors without error and returns meaningless coordinates. Such manifolds survive in users' caches and in saved sessions, so two mechanisms stay:
- The descriptor families write different columns. chembed writes
chembed_embedding, never the LSTM'sautoencoder_embedding:_compute_descriptorsreuses an existing descriptor column as a cache without recording which model wrote it. - Manifolds carry a
descriptor_backendtag, checked byvalidate_gtm_descriptor_backendbefore projection. One taggedautoencoderis refused; one published before the tag existed passes with a warning.
How little the shape tells you: encoding aspirin with both engines gave two
(1, 256) vectors whose cosine similarity is 0.13. Same size, otherwise
unrelated, so nothing but the tag and the column name can catch a swap.
The published manifold, axelrolov/chemspacecopilot-gtm-chembed
(HUGGINGFACE_GTM_CHEMBED_REPO / _REVISION, cached in
~/.cache/cs_copilot/models/gtm-chembed), is fitted on chembed latents of a
pinned 50,000-molecule frameset. It is pinned by revision rather than tracking a
branch, so a repository update cannot exchange it under results that depend on
it. resolve_gtm_model_path resolves the default map to it; a session that has
pinned its own map keeps that one. scripts/refit_gtm_manifold.py refits a
manifold, reusing the published hyperparameters rather than re-tuning them.
Adding a tool¶
- Implement or extend a toolkit in the appropriate scientific package and register only the methods intended for Agno.
- Add the toolkit to the owning agent factory. A new toolkit class also joins
its group in
src/cs_copilot/capabilities.py, and a role that needs a new group gets it there. - If MCP should expose the operation, add its tool specification (named with its group's prefix), facade or adapter binding, and annotations. Roles and profiles are derived from the capability table; do not set them on the spec.
- Update affected skills and workflow contracts when the operation changes a procedure, permission, or artifact contract.
- Test deterministic behavior, role access, direct-tool/MCP parity, profile filtering, and artifact registration as applicable.