Skip to main content

Trackinizer

PyPI version CI Python 3.12+ License: Apache-2.0 Discord

Centralized agent database for inquiries (Issues + Artifacts), work, and knowledge. Three storage tables (inquiries, edges, change_log) backed by Postgres (real or PGlite). FastAPI on top.

types/ is the design contract. Every other module is a realization of that contract over Postgres + HTTP.

The UI

The optional SPA (server/web.py) browses the same records the API serves.

Graph — the whole inquiry web (Issues, Beliefs, Papers, Experiments, …) as typed nodes and edges.

Force-directed graph of the inquiry web

Console — live multi-agent chat, filterable by room and date.

Live multi-agent console

Belief — a record with its before/after relationship panels.

Belief record with parent/child edges

Paper — abstract, authors, and cites edges to other papers.

Paper record with abstract and citations

Experiment — outcome, labels, and links to the beliefs it proves or disproves.

Experiment record with outcome and relationships

Module layout

trackinizer/
├── README.md                       ← this file
├── docs/                           api.md (HTTP surface); old/ (design notes)
├── conftest.py                     integration fixtures (Postgres engine)
├── *_test.py, integration_test.py
│
├── types/                          design contract: pure types, no I/O
│   ├── __init__.py                 re-exports for ergonomic imports
│   ├── errors.py                   ConflictError, NotFoundError
│   ├── columns.py                  Row Protocol, ColumnSpec, FlatColumn,
│   │                                 column_specs(), flat_column_specs()
│   ├── cost.py                     Cost (signed; +/- for deltas)
│   ├── embedder.py                 Embedder Protocol
│   ├── inquiries.py                Inquiry, Issue, Artifact, Experiment,
│   │                                 Paper, Belief, CodeChange, WebResult,
│   │                                 WebSearch, results_or_empty
│   ├── edges.py                    Edge, EdgeKindPolicy, edge_policies
│   └── change_log.py               Change, Snapshot
│
├── wire/                           THE HTTP contract; imports only types/
│   ├── routes.py                   route table (inquiry_field_routes,
│   │                                 edge_field_routes), path templates,
│   │                                 KIND_URL_TOKEN -- all derived from
│   │                                 ColumnSpec metadata
│   ├── bodies.py                   Submit{Issue,...}, SubmitBatch,
│   │                                 FieldSet[T]/FieldOp[T]/FieldMutation
│   ├── edge_bodies.py              CreateEdge, CreateEdgeBatch
│   ├── filters.py                  Filter, FilterOp, FILTER_FIELD_ALIASES,
│   │                                 canonical_filter_field
│   ├── refs.py                     SeqRef, UuidRef, Ref
│   └── row_filter.py               match_filter (server + CLI-fake shared)
│
├── client/                         standalone HTTP SDK (wire/ + httpx)
│   ├── client.py                   Client: typed methods over /api/*
│   └── errors.py                   ClientError
│
├── trax/                           the `trax` CLI (a thin layer over client/)
│   ├── __main__.py, cli.py         entrypoint + dispatch
│   ├── grammar.py                  CLI token vocabulary, field/alias tables
│   ├── parser.py                   tokens → typed Actions / ListQuery
│   ├── verbs.py                    per-verb command handlers
│   ├── render.py, commands.py, profile.py
│   └── run/                        `trax run` agent-CLI session runner
│
└── server/                         the FastAPI server + storage (imports
    │                                wire/ + types/; never imported by them)
    ├── __main__.py                 entrypoint: arg parsing, uvicorn, web mount
    ├── api/
    │   ├── app.py                  FastAPI() + lifespan + exception handlers
    │   ├── submit.py               POST /api/inquiries/{kind} (+ /batch)
    │   ├── edit.py                 PUT/PATCH/DELETE /api/inquiries/{id}/{field}
    │   ├── edge.py                 /api/edges/{from}/{kind}/{to}[/{field}]
    │   ├── query.py                GET /api/inquiries*, /api/change_log*
    │   ├── auth_routes.py, oauth_routes.py, admin_routes.py
    │   ├── idempotency.py          Idempotency-Key → change_log.id middleware
    │   └── _deps.py, _routes_shared.py
    ├── store.py                    Store (orchestrator) + StubEmbedder
    ├── primitives.py               insert_inquiry/edge, upsert_embedding,
    │                                 reject_edge_cycle, validate_list_references
    ├── setter_dispatch.py          COLUMN_SPECS, RUNTIME_HOOKS; the table
    │                                 Store._set_field dispatches against
    ├── projection.py               row + edges → typed Inquiry
    ├── schema_gen.py               generated DDL (kind columns, change_log
    │                                 mirror, kind matrix, per-kind sequences)
    ├── sql.py                      schema migration loader (assets/*.sql)
    ├── sql_fragments.py            _NEXT_ISSUE_SQL, _COST_SUBTREE_SQL, ...
    ├── migrations.py               numbered data migrations
    ├── values.py                   canonical_strs, list_or_none, ...
    ├── notify.py                   NOTIFY_CHANNEL, tx, post-commit fanout
    ├── auth.py, session.py         bearer/session auth, principals
    ├── config.py                   Config, build_engine, build_embedder
    ├── web.py                      optional SPA mount + /api/web/* reads
    └── assets/                     index.html SPA + schema.sql + *.html

Package dependency graph

Four layers, two legs sharing one contract spine. An arrow means "imports / depends on" and points toward the dependency.

┌──────────────┐                      ┌──────────────┐
│     trax     │                      │    server    │   leaves: nothing
│  (CLI)       │                      │ (__main__)   │   imports these
└──────┬───────┘                      └──────┬───────┘
       │                                     │
       ▼                                     ▼
┌──────────────┐                      ┌──────────────┐
│    client    │                      │     api      │   server leg adds
│  (httpx SDK) │                      │  (FastAPI    │   Store; client leg
│              │                      │   handlers)  │   adds httpx
└──────┬───────┘                      └──────┬───────┘
       │                                     │
       └──────────────┬──────────────────────┘
                      │  both import
                      ▼
              ┌───────────────┐
              │     wire      │   transport contract = THE API
              │  bodies       │   definition (request/response
              │  routes       │   models, route table, filters, refs)
              │  filters      │
              │  refs         │
              └───────┬───────┘
                      │
                      ▼
              ┌───────────────┐
              │     types     │   domain dataclasses;
              │  inquiries    │   imports nothing internal
              │  edges        │
              │  change_log   │
              │  cost         │
              └───────────────┘

Two legs, one shared spine:

  • client leg: traxclientwiretypes
  • server leg: server/__main__server/apiwiretypes

The whole point of this split: types/ + wire/ + client/ form a self-contained client distribution. wire/ and client/ never import api, server, store, web, fastapi, asyncpg, or trax, so a published Python client carries no server dependencies.

Layering rules

  1. types/ is a closed island. Nothing in types/ imports anything outside it. Everything else eventually imports from it. The dataclasses, Protocols, and ColumnSpec metadata in types/ are the data design contract; the rest of the package realizes it.

  2. wire/ is the API contract; it is the single definition of the HTTP surface. It holds the Pydantic request/response bodies (FieldSet[T], FieldOp[T], FieldMutation, Submit*, edge bodies), the Filter/Ref shapes, and the route table the server registers from and the client builds requests from. It imports types/ and nothing else internal. Server and client both derive from it, so neither hand-writes a path or a body shape -- that is what prevents server/client/doc drift.

  3. client/ is the standalone SDK; trax is a thin CLI over it. client/ is wire/ + httpx. trax owns only CLI concerns (grammar, parsing, rendering) and calls client.Client. Neither client/ nor wire/ may import the server side or the CLI.

  4. No cycles. Each arrow above goes one direction. server/primitives.py imports server/setter_dispatch.RUNTIME_HOOKS and mutates it at import time (late-binding the results / codechanges validators). server/store.py imports primitives, so the side effect lands before any Store instance is constructed.

  5. server/sql.py is orthogonal. It loads assets/schema.sql from disk. server/schema_gen.py substitutes generated bodies into the loaded text at bootstrap; the two never import each other.

  6. server/api/ is the HTTP boundary. Routes are thin: pydantic-validate the wire body, call one Store method, serialize the result. Anything non-trivial belongs in server/store.py, not in a route.

Reading order

Pick one of these orders depending on what you came in for.

  • "What does Trackinizer model?" Start in types/. Read inquiries.py, then edges.py, then change_log.py. Read docs/design.md for the model and philosophy.

  • "What is the HTTP API / how do I avoid drift?" Start in wire/routes.py: the route table is derived from the ColumnSpec metadata in types/inquiries.py. The server registers handlers by iterating it (server/api/edit.py, edge.py), the client builds requests from it (client/client.py), so neither hand-writes a path. server/api/routes_drift_test.py and assets_drift_test.py fail if a handler or the SPA diverges from the table.

  • "How does a submit reach the database?" server/api/submit.pywire/bodies.py (body validation) → server/store.py (submit_X) → server/primitives.py (insert_inquiry, insert_edge).

  • "How does an edit fan out notifications?" server/api/edit.pyserver/store.py (set_X_set_field) → server/setter_dispatch.py (RUNTIME_HOOKS[col]) → server/notify.py (post-commit buffer + LISTEN/NOTIFY).

  • "How does the schema get built?" server/__main__.pystore.bootstrap()server/sql.py (schema_migrations()) → server/schema_gen.py (substitute_schema_placeholders()) → the four _generate_* functions. Generated text is derived from ColumnSpec metadata on the dataclasses in types/. See docs/db_schema_migration.md for running migrations, squashing, and deploying schema changes.

  • "How does a dependency_changed cascade work?" store.emit_changestore._cascade_dependency_changedstore._parent_edgestypes/edges.EdgeKindPolicy. The policy table is the single declaration site for which endpoint of each edge kind is the dependent.

Running

uv run python -m trackinizer.server                        # pglite (default), with web UI
uv run python -m trackinizer.server --engine pg --dsn ...  # against real Postgres
uv run python -m trackinizer.server --no-web               # API only

See example.sh for a worked end-to-end submit/edit/query session.

System dependencies

Integration tests (@pytest.mark.integration) provision a real Postgres via pytest-postgresql and require pgvector. PGlite bundles its own vector extension, so the default pglite engine has no system deps.

sudo apt-get install -y postgresql postgresql-18-pgvector   # Ubuntu/Debian
brew install postgresql@17 pgvector                         # macOS

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

trackinizer-0.1.1.tar.gz (1.1 MB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

trackinizer-0.1.1-py3-none-any.whl (831.8 kB view details)

Uploaded Python 3

File details

Details for the file trackinizer-0.1.1.tar.gz.

File metadata

  • Download URL: trackinizer-0.1.1.tar.gz
  • Upload date:
  • Size: 1.1 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for trackinizer-0.1.1.tar.gz
Algorithm Hash digest
SHA256 6ee60513cf41c8b656241557e38cb69f5f2f43f7384baa344642e57b61e4f8f7
MD5 adfde083cfa0f55a805c474b4a278898
BLAKE2b-256 e1b49db4d8e2fe948d020aaa78a8f8466b67d541ca299e20bfe46b29a6905ae9

See more details on using hashes here.

Provenance

The following attestation bundles were made for trackinizer-0.1.1.tar.gz:

Publisher: publish-pypi.yml on rekursiv-ai/trackinizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file trackinizer-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: trackinizer-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 831.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for trackinizer-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 a115b729dc176e574b3d8d2a86516ba4210a0d292199efc5822cd056ebe14889
MD5 79854d57927c2f05ec4f3ae5ee08f5a1
BLAKE2b-256 029d616c9de5e94dbaa2d487364a576540e0f3ba4dce126319f3874f063d2286

See more details on using hashes here.

Provenance

The following attestation bundles were made for trackinizer-0.1.1-py3-none-any.whl:

Publisher: publish-pypi.yml on rekursiv-ai/trackinizer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.1.4

2 files

0.1.3

2 files

0.1.2

2 files

This release

0.1.1 This release

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page