Skip to content

local-agent

A local-first org-navigator — a personal tool for capturing and querying your working relationships across a large company, with no data leaving the machine.

This is a distinct application. It does not depend on a cloud model to function. Glovebox is a future dependency for the optional L2 (aggregate-strategy) layer — it is not part of v0.

Architecture context: glovebox/docs/architecture/local-first-egress.md (the L1/L2 split, the egress-firewall model, and why the local agent is the single interface and trust boundary).


Status: v0 built (slices 1–3)

The store, the query layer, and the terse CLI are done and tested. The next step is slice 4 — actually using it and finding the friction. No model, no cloud.

CLI cheatsheet for the full command reference.

Quickstart

# from this directory (uv manages the venv automatically)
uv run local-agent person "Me" --me                     # set the anchor first
uv run local-agent person "Alice Chen" --team SPEAR --role "Staff Eng"
uv run local-agent link Me Alice
uv run local-agent note "Needs feature X" --for Alice --owe --kind requirement

uv run local-agent who SPEAR        # who I know on a team (1st/2nd degree)
uv run local-agent brief Alice      # dossier before a 1:1
uv run local-agent owe              # what I'm behind on

The store is a single SQLite file: --db PATH, else $LOCAL_AGENT_DB, else ~/.local/share/local-agent/store.db (auto-created). Back it up by copying that file. Tip: use the ./la wrapper (or alias la='…/tool-chest/local-agent/la') so a stale VIRTUAL_ENV from an old checkout does not warn; or pip install -e . for a bare local-agent.


Chat capture (local model)

For lower-friction capture, talk to a local model instead of typing CLI flags. The harness is Claude Code pointed at an Ollama model via Ollama's Anthropic-compatible API on localhost:11434; a skill teaches it to translate plain sentences into la commands. Everything stays on the machine.

Set your model once in ../tool-chest.toml:

[local_agent]
model = "mistral:7b"   # change to whatever you pulled

Then pull and (optionally) build a larger-context variant:

# one-time: install Ollama, pull a tool-use-capable model sized to your RAM
ollama pull mistral:7b             # or whatever is set in tool-chest.toml
# (Claude Code needs >=64k context — optionally build a bigger-context variant)
ollama create local-agent-chat -f Modelfile   # see Modelfile
# start chatting (interactive)
./chat                                    # uses model from tool-chest.toml
LOCAL_AGENT_MODEL=local-agent-chat ./chat # one-off override

# then just talk:
#   "I'm Nathan, set me as me"
#   "met Priya, staff eng on SPEAR; I owe her the API doc Friday"
#   "who do I know on SPEAR?"

Model sizing rule of thumb: ~4B for 8 GB RAM, ~9B for 16 GB, ~27B for 24 GB+.

Privacy contract

The local chat path is designed so no content leaves the machine:

  • Local model only. ./chat and the audit refuse :cloud model tags (those route off-box).
  • All API traffic to localhost. scripts/privacy_env.sh reads model and URL from ../tool-chest.toml, then pins every Claude Code model tier (main + housekeeping) to Ollama on localhost:11434, empties ANTHROPIC_API_KEY, and disables telemetry — so background "housekeeping" calls cannot reach Anthropic.
  • Locked tool surface. .claude/settings.json allows only la shell commands and denies WebFetch/WebSearch/curl/wget.
  • Caveat. Claude Code itself is closed-source; the boundary is "correctly-configured Claude Code on a local model", verified by the audit below.

Prove it: on-demand egress audit

Trust is testable, not assumed. Run the audit any time:

make audit            # Layer A: config attestation (no sudo)
sudo -E make audit    # + Layer B (deny-egress functional proof) + Layer C (packet evidence)
  • Layer A — asserts the model is local and the env/permissions lock egress; hashes the controlling files.
  • Layer B — blocks all non-loopback egress with the macOS pf firewall, runs a real headless capture, and asserts the row still landed. If capture works with the network cut off, the content path needs only localhost.
  • Layer C — sniffs a normal run and asserts zero non-loopback packets.

Each run writes security-attestation.md (SPB v1 format) and appends to audit/egress-audit-log.md (append-only, SPB-12). Layers that can't run (no sudo/ollama) report SKIPPED, never PASS. See the workspace Security & Privacy Baseline (SPB-11/12/13).


What this is (and is deliberately not)

The honest framing, set by a pre-mortem: reliable-but-narrow beats impressive-but-fragile. The most valuable part of the whole system, for the actual goal (navigating a big org's opaque structure), is this minimal local store + a few good queries + frictionless capture. The clever parts (cloud strategy, an LLM router, adaptive orchestration) are later layers that must earn their place.

Local-model reliability is an experiment to run, not a promise to make. So v0 contains no model at all.

v0 scope (LLM-free, local-only)

  • A canonical local store of people, relationships, and notes/requirements.
  • Low-friction capture — terse one-line commands. This is the make-or-break feature; if logging "Bob needs X" mid-day feels like a chore, the system dies.
  • Three deterministic queries, mapped to real needs:
  • who do I know on <team>
  • 1st / 2nd-degree connections to <team or person>
  • open follow-ups / requirements (by person or team)

Explicitly NOT in v0

No LLM, no cloud, no Glovebox, no router, no calendar/email import, no vector search. Each is a later layer gated on v0 proving itself in daily use.

Success criterion (the bar)

Within the first week on the job, this is used to answer a real "who do I know on X?" about a real colleague, and capturing a note feels light. If that holds, later layers are justified. If not, the real constraint was capture, not intelligence — and we learned it in days.


Data architecture

Three data shapes, one embedded engine — not three databases.

Shape Where it lives Notes
Tabular — people, attributes, notes SQLite (the canonical file; system of record) stdlib sqlite3, one file, no server
Graph — relationship edges SQLite edge table → networkx in-memory for traversal personal scale (hundreds–low-thousands of nodes); BFS/centrality are instant
Vector — embeddings for retrieval reserved seam, not built in v0 switches on when a local model arrives and needs RAG (likely sqlite-vec)

The agent talks to a repository interface (get_people, neighbors(person, depth), add_note, and a reserved semantic_search) — never to SQLite directly — so the storage engine stays swappable and the vector index can slot in later without touching agent logic.

Multi-tenancy / scale-up is out of scope unless the L2 experiments prove good enough results to warrant it.

Schema (canonical store)

  • people — id, name, aliases, team, org, role, contact, tags, is_me, timestamps
  • edges — source, target, kind (reports_to | collaborates_with | knows), weight, provenance, timestamp
  • notes — person_id (nullable for team-level), text, kind (requirement | followup | observation), status (open | done), due_date, provenance, timestamp
  • embeddings (reserved, unpopulated in v0) — entity_type, entity_id, vector, model

provenance is where future capture sources (manual, calendar, work email, AnyType) record where a fact came from.


Build sequence

Slice What Status
1 SQLite store + schema + repository interface ✅ done
2 Query layer: neighbors / connections_on_team / intro_path / dossier / commitments (networkx over the store) ✅ done
3 Low-friction capture + query CLI (local-agent) ✅ done
4 Use it for real, iterate on friction ← here (real use, week 1)

Later (each gated on the prior earning its place): local model + context engineering + adaptive orchestration; the vector seam; the Glovebox-mediated L2 escalation; the optional per-turn router.

Engineering rhythm: cautious TDD, one slice at a time, verified gates, and the forward/backward (backpass) pass on each slice.