Metadata-Version: 2.5
Name: spatialagents
Version: 0.2.4
Summary: The chat harness runtime SCIMAP Pro, Plexora and other BioCognia products share
Author-email: Ajit Johnson Nirmal <ajitjohnson.n@gmail.com>
License-Expression: LicenseRef-Proprietary
Classifier: Intended Audience :: Science/Research
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
Requires-Python: >=3.12
Requires-Dist: biocognia<0.2,>=0.1.1
Requires-Dist: pydantic>=2.13
Requires-Dist: pyyaml>=6
Description-Content-Type: text/markdown

# spatialagents

The runtime of **Biocognia Intelligence**, the AI layer BioCognia's products
share (Plexora, SCIMAP Pro): the streamed model call through the BioCognia AI
gateway, a tool loop with deferred tool schemas, approvals for source-file
writes and deletes, sub-agents, deterministic compaction, a tool-result cache,
offloaded results, the conversation store, the local trace, a terminal loop
and the Workspace page. Biocognia Intelligence is this package, the
`biocognia` client (the device's certificate, its entitlements, the AI token)
and the platform's AI gateway (task routes, credits, the request log); the
contract a product implements is `biocognia-platform/docs/intelligence.md`.

A product binds it with three things and never the other way round:

- a `spatialagents.backend.ToolBackend`: what its tools are and how one runs
  (`catalog`, `definition`, `permission(tool, arguments)`, `invoke`, ...).
  A product whose tools are plain functions lists them as `ToolSpec`s and
  subclasses `SimpleBackend`, which answers the protocol from the specs and
  the functions' signatures;
- a `spatialagents.runner.ChatProfile`: its prompt content, task names and
  wording, and its named sub-agent roles (`spatialagents.roles.Role`: the
  tools, task, skill, step bound and answer keys of one kind of sub-agent;
  `roles.load_roles` reads them from one Markdown file each). Its house rules
  start from the runtime's own (`prefix.house_rules`);
- its places on disk and its version, given to each store.

A product's gateway client says which door a call came in by
(`GatewayClient(..., origin="cli")`: the page is `app`, a terminal loop or
script `cli`), so the platform's request log tells the surfaces apart; an
unlicensed product hands the runtime a `gateway.RefusingGateway`.

```python
from biocognia.gateway import TokenSource
from spatialagents.conversations import ChatService, ConversationStore
from spatialagents.gateway import GatewayClient
from spatialagents.policy import Policy

service = ChatService(ConversationStore(root / "conversations"),
                      gateway_factory=lambda: GatewayClient(TokenSource(LICENSING)),
                      backend_factory=MyBackend, profile=MY_PROFILE)
started = service.start(Policy())
```

## The Workspace

`spatialagents.workspace` (0.2) is a session seen whole: its conversations as
branches (an edited question is a new branch, never an overwrite), its plans
run one step at a time, the results each step made (figures and tables by
URL), findings held to the results they cite, a report, a notebook of the code
that reproduces it, and checkpoints. The shared layer owns the records and the
transport; a product owns what they mean, through a `WorkspaceHost` (what an
execution read and wrote, when a result is stale, what code reproduces it,
what a finding may claim) and a `WorkspaceOpener` (where workspaces live).
An application over several products gives one opener that names them
(`homes()`); each workspace then belongs to one, the page asks which a new one
is for, and inside it the page takes that product's documentation and marks.

```python
from spatialagents.chat.server import ChatServer
from spatialagents.workspace import static_dir
from spatialagents.workspace.http import WorkspaceApi
from spatialagents.workspace.service import WorkspaceService

service = WorkspaceService(MyOpener())            # list / create / open -> WorkspaceBinding
server = ChatServer(WorkspaceApi(service), page_dir=static_dir(), title="My AI Workspace").start()
print(server.launch_url)                          # one-time token -> a page session
```

- `workspace/store.py`, `branching.py`, `artifacts.py`: the records under
  `<session>/workspace/` -- one event sequence, branches, artifacts, findings,
  checkpoints, the report.
- `workspace/scope.py`: chats. A workspace holds many: each a parentless
  branch with its own conversation on the same cached prefix, `shared` or
  `incognito`.
  - Every route takes the chat it is for.
  - `emitter.scope()` / `scope_of(ref)` tell a host which chat work is for.
  - An incognito chat's findings and report stay in it until promoted
    (`/findings/promote`).
  - `/results` lists every chat's executions with their provenance, `/search`
    searches across chats, and deleting a chat leaves a tombstone its results
    still name.
- `workspace/service.py`, `http.py`: the `/workspaces` routes, a multiplexed
  event stream (SSE or a held poll), and recovery of work a dead process left.
- `workspace/packets.py`: the few model calls outside a plan (ranking what was
  noticed, proposing findings, interpreting what discovery found), each a
  structured packet the host validates.
- `workspace/discovery.py`: one background look at a workspace's locations at
  a time (`DiscoveryRun`). A workspace made from a folder opens at once; the
  host announces what it finds (`discovery.*` events every branch sees) and
  the page's overview fills in from them (`/workspaces/:ws/discovery`).
  The page shows it as the Dataset Overview, the conversation's first cell
  (`web/src/overview.ts`, `web/src/cards/Overview.tsx`): a title, two or
  three sentences, the numbers and one primary action, with the files,
  candidates, columns and open questions under Details.
- `notebook.py`: where a page served from a notebook kernel can be reached
  (local Jupyter, a hub's proxy, Open OnDemand, Colab) and its iframe.
- `chat/server.py`: page sessions by cookie (a top-level page) or bearer (an
  iframe), `frame_ancestors`, `GET /health`, and `POST /api/launch` for a
  fresh one-time link from a page that holds a session.
- `workspace/host.py`: `GROUPS`, the methods that only work together (the
  investigation, discovery, the coding agent, ...); a host implements a group
  whole or not at all, `host_problems` names one half-built (the service logs
  it on open), and `testing.check_host(host)` is the conformance a product's
  suite runs on its own host. `handoff_kinds()` names the hand-offs the page
  offers as buttons.
- `desktop.py`: the one ready line a desktop shell reads from the page server.

The page is Vite + Preact + TypeScript in `web/`, built into
`src/spatialagents/workspace/static/dist/` and committed, so neither `pip
install` nor the Python tests need Node. It runs in a browser tab, a notebook
iframe, or a desktop shell (a Tauri app) through `web/src/desktop.ts`: the
commands `pick_paths`, `open_url`, `take_pending_opens`, `server_info`, and the
events `workspace://menu`, `workspace://opens-pending`. After changing it:

```bash
cd web && npm ci && npm test && npm run build    # CI (web.yml) fails on a stale dist
```

Build it outside a synced folder (`WORKSPACE_DIST=<repo>/src/spatialagents/workspace/static/dist`
names the output) -- `node_modules` in Dropbox is slow at best.

## Dependencies and tests

Dependencies: the standard library, `biocognia`, `pydantic` (a function's
input schema, `backend.signature_schema`) and `pyyaml` (role files), the last
two imported only when used. No `mcp`, `anndata`, Flask or numpy, and nothing
from either product; `import spatialagents` imports nothing beyond the
standard library.

Versions: products pin one minor (`>=0.2,<0.3`). Within it a release only adds
-- a protocol method or a field with a default, a new module -- so a product
that pins the minor keeps working; a required method, a removed default or a
changed wire shape waits for the next minor, and both products move together.

Tests need no product, no network and no model: `spatialagents.testing` has
`FakeGateway` (the gateway's wire and a prompt-cache emulator),
`FakeToolBackend`, `ScriptedBrain`, `FakePlanHost`, `FakeWorkspaceHost`,
`FakeWorkspaceOpener`, `workspace_stack` and `check_host`, and registers inert
pytest fixtures.

```bash
UV_PROJECT_ENVIRONMENT=$HOME/.venvs/spatialagents uv sync --all-groups
BIOCOGNIA_OFFLINE=1 $HOME/.venvs/spatialagents/bin/python -m pytest -q -p no:cacheprovider
```
