Centralized agent database for Inquiries (Issues + Artifacts).
Project description
Trackinizer
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.
Console — live multi-agent chat, filterable by room and date.
Belief — a record with its before/after relationship panels.
Paper — abstract, authors, and cites edges to other papers.
Experiment — outcome, labels, and links to the beliefs it proves or disproves.
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:
trax→client→wire→types - server leg:
server/__main__→server/api→wire→types
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
-
types/is a closed island. Nothing intypes/imports anything outside it. Everything else eventually imports from it. The dataclasses, Protocols, andColumnSpecmetadata intypes/are the data design contract; the rest of the package realizes it. -
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), theFilter/Refshapes, and the route table the server registers from and the client builds requests from. It importstypes/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. -
client/is the standalone SDK;traxis a thin CLI over it.client/iswire/+httpx.traxowns only CLI concerns (grammar, parsing, rendering) and callsclient.Client. Neitherclient/norwire/may import the server side or the CLI. -
No cycles. Each arrow above goes one direction.
server/primitives.pyimportsserver/setter_dispatch.RUNTIME_HOOKSand mutates it at import time (late-binding theresults/codechangesvalidators).server/store.pyimportsprimitives, so the side effect lands before anyStoreinstance is constructed. -
server/sql.pyis orthogonal. It loadsassets/schema.sqlfrom disk.server/schema_gen.pysubstitutes generated bodies into the loaded text at bootstrap; the two never import each other. -
server/api/is the HTTP boundary. Routes are thin: pydantic-validate the wire body, call oneStoremethod, serialize the result. Anything non-trivial belongs inserver/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/. Readinquiries.py, thenedges.py, thenchange_log.py. Readdocs/design.mdfor 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 theColumnSpecmetadata intypes/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.pyandassets_drift_test.pyfail if a handler or the SPA diverges from the table. -
"How does a submit reach the database?"
server/api/submit.py→wire/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.py→server/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__.py→store.bootstrap()→server/sql.py(schema_migrations()) →server/schema_gen.py(substitute_schema_placeholders()) → the four_generate_*functions. Generated text is derived fromColumnSpecmetadata on the dataclasses intypes/. Seedocs/db_schema_migration.mdfor running migrations, squashing, and deploying schema changes. -
"How does a
dependency_changedcascade work?"store.emit_change→store._cascade_dependency_changed→store._parent_edges→types/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
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trackinizer-0.1.0.tar.gz.
File metadata
- Download URL: trackinizer-0.1.0.tar.gz
- Upload date:
- Size: 3.2 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c671c88dc6f76555976487f214954274f8a98c35c36567e878f6b79dc9ee0051
|
|
| MD5 |
f770055a2945a1b48f807385d82636e0
|
|
| BLAKE2b-256 |
d6a7ba9601fe07004b0452169fb0c40f077158e3e9677f68e4c801874eb9ecdb
|
File details
Details for the file trackinizer-0.1.0-py3-none-any.whl.
File metadata
- Download URL: trackinizer-0.1.0-py3-none-any.whl
- Upload date:
- Size: 2.9 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f6b19729fb3d51e6dbe534a5d923a29d8aadc78e739f4c610455cadc753d9ec2
|
|
| MD5 |
901626075a8eff205ee9321b41fdd8a9
|
|
| BLAKE2b-256 |
984f722e9942c59070b87ff18715d48ee9010e8a2e86ef1d315a046044ef4a07
|