Technical deepdive

How the loop is actually built.

AgentLedger is a FastAPI + Postgres/pgvector backend, a React SPA, and one 20-tool MCP — all sitting on a single shared service layer. Here's the architecture, the agent-loop mechanics, and the data model that makes it work.

System topology

Two entry planes, one code path

Humans enter through REST with a JWT; agents enter through the MCP endpoint with a scoped API key. Both converge on the same service layer, which is the only thing that touches the database.

Web SPA
React 19 · TanStack · JWT
Agents / MCP clients
API key · JSON-RPC 2.0
↓ REST routers        MCP server (20 tools) ↓
Shared service layer
app/services/* — items · memory · prds · clustering · prioritization · drive_sync
↓ SQLAlchemy models · Alembic
Postgres + pgvector
dialect-aware EmbeddingType · ivfflat cosine index

Why the service layer is the keystone

Routers and the MCP server are thin adapters — every tool call and every REST request runs the same function. There is exactly one path to the database, so an agent's create_item appears in the web tracker with no reconciliation, and every capability is available to both people and agents for free.

The differentiator

The agent loop, mechanic by mechanic

prd_coverage
specced but unbuilt
decompose_prd
spec → tasks
get_backlog
dep-aware, code-local
claim_next
atomic, leased
update done
→ memory
A · Claiming & leases

Collision-free assignment

claim_next reads the best ready candidate then does an optimistic UPDATE guarded on claimed_by — only one caller wins the row, so two agents never claim the same item. A claim carries a lease (claimed_at); heartbeat extends it, and a lease that goes stale past the window is reclaimable, so a crashed agent's in-progress work is picked up by another. agent_id defaults to the API key's name — one key, one agent.

B · Touchpoints & clusters

Batching by code-locality

Items declare touchpoints — files, globs, or modules they affect. Two items relate when touchpoints overlap (exact, glob, or same directory), and overlap auto-creates a code link. next_cluster claims the best ready item plus its related ready neighbours in one call, so an agent pulls a whole code-neighborhood and works it in one context instead of thrashing.

C · Prioritization

Priority from the dependency graph

Readiness is computed, not flagged: an item is blocked until every item it depends on is done. The backlog sorts ready-first, then by a composite score — status, dependency fan-out (unblocking many ranks higher), request votes rolled onto the linked item, effort, and staleness. claim_next and next_cluster consume the same ranking, so agents always take the highest-leverage ready work.

D · Spec coverage

The spec is the source of truth

Items link to a PRD section. decompose_prd parses a spec's sections and creates a tracked task per gap, linked back to the section; prd_coverage returns the per-section rollup — counts by status, percent done, and the sections with no tasks yet. Completing work updates coverage, and the loop asks the spec what's next.

Institutional memory

When an item is completed, AgentLedger auto-extracts its lessons into pgvector memory. A later agent's search_memory surfaces those decisions semantically — so the loop compounds knowledge instead of relearning it.

Agent interface

The 20 MCP tools

JSON-RPC 2.0 over POST /api/mcp, authed by a project-scoped key. Every tool ships a typed outputSchema, read-only / destructive annotations, and structured errors; creates take idempotency keys and reads paginate.

ToolKindPurpose
Orientation
get_contextreadThe key's project, scopes, project/tool counts — call first
list_projectsreadProject ids for the project_id override
Work queue
claim_nextwriteAtomically claim the best ready item (leased)
next_clusterwriteClaim a whole code-neighborhood at once
heartbeatwriteExtend the lease on a claimed item
release_itemwriteReturn a claim to the queue
get_backlogreadPrioritized backlog — ready-first, with blocked_by / unblocks / votes / score
suggest_nextreadSingle best next item, dependency-aware
Items
create_itemwriteCreate a task (tags, touchpoints, PRD link) · idempotent
update_itemwritePatch / advance status
search_itemsreadQuery by text, tags, status (paginated)
get_item_detailsreadItem + linked shards + requests
related_workreadThe code-neighborhood around a task
link_itemswriteTyped link (dependency / code / semantic / tag)
Memory · Spec · Insight
add_memorywriteAttach a memory shard · idempotent
search_memoryreadSemantic (pgvector cosine) search over shards
decompose_prdwriteSpec sections → tracked tasks
prd_coveragereadPer-section rollup + gaps
extract_lessonswriteDistill an item's lessons into memory
generate_digestreadProgress digest across the project

Persistence

Data model

Everything is project-scoped; items is the hub the loop turns on.

TableCarries
itemsstatus, tags, effort, blocker · touchpoints (B) · assignee / claimed_by / claimed_at (A) · prd_id / prd_section (D) · github_url, pr
linkstyped edges — dependency / code / semantic / tag (drives C's ready-set and B's clusters)
memory_shardspgvector embeddings — semantic recall; auto-written on completion
prds · prd_versionsmarkdown specs with version history; sections drive coverage (D)
requests · attachmentspublic feedback triage — votes roll onto linked items (C); image attachments by id
api_keysscoped to a project — one key = one agent
sync_stateper-PRD last-synced hash for Drive conflict detection
platform_configper-project AI provider, integrations, spam settings

Alembic chain 0001 → 0012 applies clean on a fresh database.

Reach

Integrations

Runtime

Stack & deployment