AgentsView
AgentsView indexes coding-agent session transcripts (Claude Code, Codex, Copilot CLI, and others) and reports token usage, cost, and searchable history.
It is local-first: every workstation runs its own copy against its own SQLite archive. This deployment provides a shared aggregation point. Each workstation pushes its sessions into a central PostgreSQL database, and the cluster serves one read-only dashboard over the union of them at https://agentsview.${SECRET_DOMAIN}.
Architecture
workstation A β agentsview pg push ββ
workstation B β agentsview pg push ββΌββΊ datasci16 (PostgreSQL) βββ agentsview pg serve (this app)
workstation C β agentsview pg push ββ β² β
datasci-db.${SECRET_DOMAIN} agentsview.${SECRET_DOMAIN}
(LAN, 10.2.118.7) (envoy-internal)
- Sync direction is one-way. Workstations push; nothing flows back. The dashboard is read-only β starring and pinning happen locally and travel with the next push.
- The pod holds no state.
pg servereads everything from PostgreSQL and runsEnsureSchemaon startup, so it comes up cleanly against an empty database and needs no PVC or volsync. pg pushspeaks the PostgreSQL wire protocol, not HTTP. Workstations therefore connect to the database directly, not through the gateway.
Configuration
| Setting | Value |
|---|---|
| Image | ghcr.io/kenn-io/agentsview |
| Mode | PG_SERVE=1 β entrypoint runs agentsview pg serve |
| Database | agentsview on the datasci16 cluster |
| Connection string | POSTGRES_URL from the database-agentsview secret, templated in db/postgres.yaml to carry sslmode=require |
Data dir (AGENTSVIEW_DATA_DIR) |
/data, an emptyDir β holds only config.toml |
| Ingress | envoy-internal, Authelia OIDC via SecurityPolicy |
--public-url=https://agentsview.${SECRET_DOMAIN} is not cosmetic. The server validates the Host header on every /api/ request against the addresses it bound to, as DNS-rebinding protection. Requests arriving through Envoy carry the public hostname, so without this flag the UI loads but every API call returns 403.
Client setup
Run this once per workstation.
Read the role and password the operator generated. It is one shared role, so every workstation uses the same credentials:
kubectl get secret database-agentsview -n datasci -o jsonpath='{.data.POSTGRES_URL}' | base64 -dThat URL is not usable as-is. Its host is
datasci16-rw, the in-cluster service name, which resolves only inside thedatascinamespace β off-cluster the lookup fails. Take the role and password from it and discard the rest.Add a
[pg]section to~/.agentsview/config.toml, using the LAN hostname and port:[pg] url = "postgresql://<role>:<password>@datasci-db.${SECRET_DOMAIN}:5432/agentsview?sslmode=require"Append it at the end of the file. A TOML table header captures every key below it, so
[pg]placed above existing top-level keys such asauth_tokensilently reparents them.Keep
sslmode=require: the client refuses a DSN that permits plaintext to a non-loopback host.machine_nameis optional and defaults to the OS hostname; set it only when that hostname is not what you want labelling this machineβs sessions.AGENTSVIEW_PG_URLworks too if you would rather keep the URL out of the file.Seed the database, then install the watcher so it stays current:
agentsview pg push agentsview pg service installagentsview pg push --watchruns the same loop in the foreground.
Semantic search
Workstations embed their own sessions with OpenAI text-embedding-3-small and push the vectors with pg push. The hub embeds nothing; it calls the same OpenAI endpoint only to encode each search query. A session becomes semantically searchable once the workstation that owns it has embedded and pushed it. Text search works without any of this, and keeps working when semantic search is unavailable.
Semantic search needs three things.
- The
vectorextension in theagentsviewdatabase.pgvectorships in the CNPG image β thesystemtag builds from thestandardstage, which installspostgresql-${PG_MAJOR}-pgvector. Butvectoris not a trusted extension, so theagentsviewowner role cannot create it. ThePostgresCR in db/postgres.yaml asks the operator to create it, which works only if the operator connects as a role holding superuser. If it doesnβt, the CR reports the failure and a superuser does it once by hand:
kubectl exec -n datasci "$(kubectl get pods -n datasci -l cnpg.io/instanceRole=primary -o name)" -- psql -d agentsview -c 'CREATE EXTENSION IF NOT EXISTS vector'Either way nothing crashes without it: AgentsView attempts the same statement during schema setup and logs a one-line notice when it is refused.
halfvec needs pgvector 0.7.0 or newer; \dx in psql shows the installed version.
The
[vector]block in app/config/config.toml. An init container copies that file to/data/config.tomlon every pod start β a copy rather than a mount, because the server writes a generated auth token back into it and treats a write failure as fatal. Model identity (model,dimension,max_input_chars, and any prefixes or suffixes) must match every workstation exactly; those fields form the generation fingerprint, and a mismatch leaves the hub with no vectors it can compare against. Transport settings (endpoint,api_key_env, timeouts) may differ.agentsview pg vectors listshows which generations PostgreSQL holds.The OpenAI API key, in the
openai-api-keyfield of thedatasci.agentsview1Password item. app/secret-embeddings.externalsecret.yaml syncs it into theagentsview-embeddingssecret, and the HelmRelease exposes it asAGENTSVIEW_OPENAI_API_KEY.
pg serve matches its config to a pushed generation only at startup. If the pod starts before any workstation has pushed vectors, restart it after the first push.
On a workstation, add the same [vector] block to ~/.agentsview/config.toml, with api_key_env naming a variable the local agentsview serve process can read. Run agentsview embeddings build once to embed the existing archive; after that the daemon embeds new sessions after each sync, and pg push uploads them.
Recovering from a schema incompatibility
pg serve migrates the schema forward on startup, but refuses to run against one it considers incompatible β most likely after an image bump. It exits with pg serve: schema incompatible and names the fix. There is no reset subcommand; the schema is dropped by hand.
Nothing here is recoverable from PostgreSQL alone, but nothing is lost either: every workstation still holds the authoritative SQLite archive, and a full push rebuilds the hub from them.
Suspend the app so it stops crash-looping into a half-dropped schema:
flux suspend hr agentsview -n datasciDrop the schema. It is named
agentsview(the[pg] schemadefault) and holds only pushed data:kubectl exec -n datasci "$(kubectl get pods -n datasci -l cnpg.io/instanceRole=primary -o name)" -- psql -d agentsview -c 'DROP SCHEMA IF EXISTS agentsview CASCADE'Resume.
EnsureSchemarecreates the tables at the current version on startup:flux resume hr agentsview -n datasciRepopulate from every workstation.
--fullis required β an incremental push would skip sessions it believes are already there:agentsview pg push --full
Until each machine has run step 4, the dashboard shows only the machines that have.
Dependencies
cnpg-cluster-datasciβ thedatasci16PostgreSQL clusterext-postgres-operator-datasciβ provisions the database and role from thePostgres/PostgresUserCRs indb/envoy-gateway(envoy-internal) andautheliafor ingress and loginexternal-secretsβ pulls the OIDC client secret fromdatasci.agentsview
Gotchas
- Every workstation shares one database role. The operator provisions a single owner per database, so pushes are not attributable by credential β only by the
machinename each client sets. - Deletes do not propagate. Removing a session locally leaves it in PostgreSQL; upstream expects manual SQL cleanup.
- Text search needs nothing.
EnsureSchemacreatespg_trgm, which is a trusted extension the database owner installs on its own. - Bumping the image can require a schema migration.
pg servemigrates forward on startup but refuses to run against a schema it considers incompatible; upstreamβs answer is to drop and recreate the schema, then re-push from each machine.