Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions docs/ai/design/2026-08-13-feature-agent-registry-sqlite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
phase: design
title: System Design & Architecture
description: Define the technical architecture, components, and data models
---

# System Design & Architecture

## Architecture Overview

```mermaid
graph TD
Start["agent start"] --> Registry["AgentRegistry"]
List["agent list/detail/console"] --> Manager["AgentManager.listAgents"]
Manager --> Registry
Registry --> Conn["database/connection"]
Conn --> Schema["database/schema"]
Conn --> DB[("~/.ai-devkit/agents.db")]
Manager -->|live discovery repopulates| Registry
Registry --> Process["process liveness prune"]
```

`AgentRegistry` remains the public agent-storage boundary, but SQLite mechanics are isolated in a small database layer modeled after the memory/task packages. `AgentManager`, adapters, and CLI services continue using `register`, `registerBatch`, `lookup`, `list`, `rename`, and `prune`.

## Data Models

SQLite database: `~/.ai-devkit/agents.db`

Table: `agents`

| Column | Type | Notes |
|---|---|---|
| `name` | TEXT NOT NULL | User-facing agent name |
| `type` | TEXT NOT NULL | Provider type |
| `pid` | INTEGER NOT NULL | Live provider process PID |
| `tmux_session` | TEXT NOT NULL DEFAULT '' | Managed tmux session name, if known |
| `cwd` | TEXT NOT NULL DEFAULT '' | Working directory |
| `started_at` | TEXT NOT NULL | ISO timestamp from first known registry row |
| `session_id` | TEXT NOT NULL DEFAULT '' | Provider session id or `pid-<pid>` fallback |
| `session_file_path` | TEXT NOT NULL DEFAULT '' | Provider transcript path when known |
| `updated_at` | TEXT NOT NULL | Last registry update time |

Constraints:

- `PRIMARY KEY (type, pid)` to enforce one live row per provider process.
- `UNIQUE(name)` keeps name resolution stable and supports existing rename conflict behavior.

Legacy JSON behavior:

- Existing `agents.json` files are ignored rather than imported.
- Running agents are repopulated into SQLite through normal `agent list` discovery and `agent start` registration.
- This avoids resurrecting stale JSON rows after all DB rows have been pruned or stopped.

## API Design

Keep the current TypeScript API:

- `register(entry: RegistryEntry): void`
- `registerBatch(entries: RegistryEntry[]): void`
- `rename(currentName: string, newName: string): void`
- `lookup(name: string): RegistryEntry | null`
- `list(): RegistryEntry[]`
- `prune(): void`
- `isAlive(entry: RegistryEntry): boolean`

Merge behavior for same `type + pid`:

- Preserve existing `name` by default.
- Replace `name` when the incoming entry has a non-empty `tmuxSession`; this represents `agent start` registering a managed, user-provided name.
- Preserve existing `tmuxSession` when incoming `tmuxSession` is empty.
- Preserve existing `startedAt`.
- Update `cwd`, `sessionId`, and `sessionFilePath` from incoming values when they are non-empty.

## Component Breakdown

- `packages/agent-manager/src/utils/AgentRegistry.ts`
- Own agent-specific behavior: merge/upsert rules, lookup, rename, prune, and mapping between DB rows and `RegistryEntry`.
- `packages/agent-manager/src/database/connection.ts`
- Own SQLite path resolution, directory creation, connection setup, pragmas, query helpers, transactions, and close behavior.
- `packages/agent-manager/src/database/schema.ts`
- Own migration discovery, `user_version` tracking, and applying pending SQL migrations.
- `packages/agent-manager/src/database/migrations/001_initial.sql`
- Create the initial `agents` table.
- `packages/agent-manager/src/database/index.ts`
- Re-export the database boundary for the package.
- `packages/agent-manager/src/AgentManager.ts`
- Continue building registry entries after adapter detection.
- Benefit from PID-aware upsert without major call-site changes.
- Tests
- Update `AgentRegistry` tests from JSON parsing expectations to SQLite behavior.
- Add regression coverage for duplicate PID/name preservation and concurrent registry instances.

## Design Decisions

- SQLite over JSON lock files: SQLite provides transactional writes and file locking without a custom lock protocol.
- Separate connection/schema modules over putting all storage mechanics in `AgentRegistry`: follows the existing memory/task package pattern and keeps future migrations localized.
- SQL migration files over embedded DDL: matches the existing memory/task package layout and keeps future schema changes append-only.
- Preserve API shape: reduces blast radius across CLI services, adapters, and tests.
- Ignore legacy JSON rather than importing it: live discovery can repopulate running agents, and avoiding import prevents stale rows from returning after prune/stop.
- `type + pid` primary key: matches live process identity and directly prevents the observed duplicate rows.
- `UNIQUE(name)`: keeps rename and lookup semantics explicit. If a stale row owns a desired name, existing rename/start logic can prune first.

## Non-Functional Requirements

- Registry operations must be fast enough for console polling; expected row count is small.
- SQLite initialization must be idempotent.
- Storage writes must be atomic under multiple CLI processes.
- No secrets are stored; all data is local process/session metadata.
79 changes: 79 additions & 0 deletions docs/ai/implementation/2026-08-13-feature-agent-registry-sqlite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
---
phase: implementation
title: Implementation Guide
description: Technical implementation notes, patterns, and code guidelines
---

# Implementation Guide

## Development Setup

- Active worktree: `.worktrees/feature-agent-registry-sqlite`
- Branch: `feature-agent-registry-sqlite`
- Dependencies: `npm ci`

## Code Structure

- `packages/agent-manager/src/utils/AgentRegistry.ts`
- Agent registry behavior and SQLite row mapping.
- Merge/upsert, lookup, list, rename, prune.
- `packages/agent-manager/src/database/connection.ts`
- SQLite connection wrapper, path resolution, pragmas, query helpers, transactions, and close behavior.
- `packages/agent-manager/src/database/schema.ts`
- Migration discovery, schema initialization, and `user_version` management.
- `packages/agent-manager/src/database/migrations/001_initial.sql`
- Initial SQLite table migration.
- `packages/agent-manager/src/database/index.ts`
- Database module exports.
- `packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts`
- Primary unit coverage for storage and merge semantics.
- `packages/agent-manager/src/__tests__/AgentManager.test.ts`
- Manager-level regression coverage for list-driven registry writes.

## Implementation Notes

### Core Features

- Use `better-sqlite3` synchronously; this package is already a dependency of `@ai-devkit/agent-manager`.
- Keep SQLite connection, schema setup, and SQL migrations in `src/database/*`, following the memory/task package pattern.
- Copy `src/database/migrations` into `dist/database/` during package build so runtime schema initialization can load SQL files after SWC compilation.
- Keep `AgentRegistry.default()` and constructor injection for tests.
- Constructor should accept the existing path argument for compatibility. If callers pass `.../agents.json`, derive DB path by replacing `.json` with `.db`; if callers pass another path, use it as the database path unless a JSON extension clearly indicates legacy path intent.
- Initialize schema in the constructor or lazily before the first operation.
- Do not import legacy `agents.json`; running agents repopulate SQLite through normal discovery/start registration.

### Implemented Files

- `packages/agent-manager/src/utils/AgentRegistry.ts` now stores rows in SQLite with `PRIMARY KEY (type, pid)` and a unique name constraint.
- `packages/agent-manager/src/database/*` now owns the SQLite connection wrapper, schema initialization, and path derivation.
- `packages/agent-manager/package.json` copies migration SQL files during build, matching the memory/task package build scripts.
- `packages/agent-manager/src/__tests__/utils/AgentRegistry.test.ts` covers schema creation, ignored legacy JSON, PID-aware merge, managed-name replacement, prune, rename, and concurrent registry instances.
- `packages/agent-manager/src/__tests__/AgentManager.test.ts` covers the observed custom-name-overwritten-by-fallback regression.

### Patterns & Best Practices

- Treat `type + pid` as canonical live identity.
- Preserve user-managed names over generated fallback names.
- Prefer incoming non-empty metadata over empty metadata.
- Keep storage errors explicit.

## Integration Points

- `AgentManager.listAgents()` should not need a major rewrite; SQLite upsert fixes duplicate rows underneath it.
- `startAgent()` continues calling `registry.register(entry)` after polling the actual provider PID.
- `killAgent()` continues resolving `tmuxSession` by `lookup(agent.name)`.

## Error Handling

- SQLite constraint errors should surface in tests and CLI because they indicate a real storage bug.
- Rename conflict behavior should remain explicit through `RenameConflictError`.

## Performance Considerations

- Use primary-key and name indexes/constraints for direct lookup.
- Keep transactions around batch registration.

## Security Notes

- Registry data is local process/session metadata only.
- No credential or prompt content is stored in the registry.
62 changes: 62 additions & 0 deletions docs/ai/planning/2026-08-13-feature-agent-registry-sqlite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
phase: planning
title: Project Planning & Task Breakdown
description: Break down work into actionable tasks and estimate timeline
---

# Project Planning & Task Breakdown

## Milestones

- [x] Milestone 1: SQLite registry storage implemented.
- [x] Milestone 2: Name-preservation and concurrency regressions covered by tests.
- [x] Milestone 3: Focused verification, docs lint, final review, and PR delivered.

## Task Breakdown

### Phase 1: Storage Foundation

- [x] Task 1.1: Replace JSON read/write implementation in `AgentRegistry` with SQLite schema initialization and row mapping.
- [x] Task 1.2: Skip legacy `agents.json` import and let live discovery/start registration repopulate SQLite.
- [x] Task 1.3: Preserve the existing public `AgentRegistry` constructor/API so call sites remain stable.
- [x] Task 1.4: Extract SQLite connection and schema concerns into `src/database/*` following the memory/task package pattern.
- [x] Task 1.5: Move schema DDL into numbered SQL migrations and copy migrations during package build.

### Phase 2: Merge Semantics

- [x] Task 2.1: Implement PID-aware upsert that preserves custom names and tmux metadata.
- [x] Task 2.2: Ensure `agent start` style entries with non-empty `tmuxSession` can claim a same-PID generated fallback row.
- [x] Task 2.3: Keep `rename`, `lookup`, `list`, and `prune` semantics compatible with existing CLI behavior.

### Phase 3: Tests and Validation

- [x] Task 3.1: Update `AgentRegistry` unit tests for SQLite behavior and ignored legacy JSON.
- [x] Task 3.2: Add `AgentManager` regression tests for repeated `listAgents()` name stability.
- [x] Task 3.3: Run focused tests, typecheck, package lint, AI docs lint, and final git diff review.
- [x] Task 3.4: Commit, push branch, and open/update a PR.

## Dependencies

- Existing `better-sqlite3` dependency in `@ai-devkit/agent-manager`.
- Existing process liveness check via `process.kill(pid, 0)`.
- Existing CLI and adapter callers must continue using `AgentRegistry` without redesign.

## Timeline & Estimates

- Storage setup: medium risk, targeted to one module.
- Merge semantics: medium risk because name conflict behavior is user-visible.
- Tests and validation: medium effort because several existing tests assert JSON-file details.

## Risks & Mitigation

- Risk: existing tests rely on `agents.json` file creation.
- Mitigation: update tests to assert registry behavior rather than storage format where possible.
- Risk: `UNIQUE(name)` conflicts with stale rows.
- Mitigation: keep existing prune-before-start flow and rename conflict checks.
- Risk: pre-upgrade managed tmux metadata exists only in `agents.json`.
- Mitigation: accept this as local ephemeral state; live discovery and new `agent start` calls populate SQLite going forward.

## Resources Needed

- Local npm workspace with dependencies installed.
- GitHub CLI or git remote access for PR delivery.
63 changes: 63 additions & 0 deletions docs/ai/requirements/2026-08-13-feature-agent-registry-sqlite.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
phase: requirements
title: Requirements & Problem Understanding
description: Clarify the problem space, gather requirements, and define success criteria
---

# Requirements & Problem Understanding

## Problem Statement

`ai-devkit agent start --name <custom>` can create a correctly named tmux session while `ai-devkit agent list` later shows a generated fallback name such as `ai-devkit-77725`. Controlled reproduction on 2026-08-13 showed two related defects in the JSON registry at `~/.ai-devkit/agents.json`:

- `AgentManager.listAgents()` can write an adapter-generated fallback entry for a live process before or concurrently with `agent start` writing the user-provided name.
- `AgentRegistry.registerBatch()` upserts by `name` only, so the same live process can have duplicate rows with the same `pid` and different names.
- Multiple commands write through the fixed temp path `agents.json.tmp`, so concurrent `agent start`, `agent list`, `agent detail`, or `agent console` writes can fail with `ENOENT` during `rename()`.

Affected users are developers supervising local agents through `agent list`, `agent send`, `agent detail`, `agent kill`, and `agent console`. The current workaround is manual registry cleanup or renaming, which is fragile because polling commands can reintroduce generated rows.

## Goals & Objectives

- Preserve user-provided agent names and tmux session metadata across repeated list/detail/console polling.
- Prevent duplicate live registry entries for the same agent process identity.
- Make registry writes robust under concurrent short-lived CLI commands and long-running console polling.
- Keep existing public `AgentRegistry` API callers working with minimal call-site churn.
- Ignore existing `agents.json` state and let live discovery/start registration repopulate SQLite, avoiding stale-row resurrection.

Non-goals:

- Do not redesign provider detection or session parsing.
- Do not move historical provider session indexes into this registry.
- Do not change print-mode agent storage in `print-agents.json`.
- Do not add a daemon or long-running registry service.

## User Stories & Use Cases

- As a developer, I want `agent list` to keep showing `agent-list-debug` after I start an agent with that name, so I can target it reliably with `agent send --id agent-list-debug`.
- As a developer using `agent console`, I want background polling not to overwrite custom names with generated fallback names.
- As a developer running multiple CLI commands, I want registry writes not to fail when commands overlap.
- As a maintainer, I want the registry to enforce one live row per process identity so bugs are caught by storage constraints rather than display ordering.
- As an existing user, I want stale `agents.json` rows not to reappear after the SQLite registry is initialized.

## Success Criteria

- `AgentRegistry` stores live agents in SQLite at `~/.ai-devkit/agents.db`.
- Repeated or concurrent registration for the same `type + pid` keeps one row.
- A non-empty existing custom `name` and `tmuxSession` are preserved when incoming detection has only a generated fallback name and empty tmux metadata.
- `agent start --name <custom>` followed by repeated `agent list`, `agent detail`, and `agent console` polling continues to list `<custom>`.
- Concurrent registry writes do not use a shared temp file and do not fail with `agents.json.tmp` rename errors.
- Existing `agents.json` entries are left in place but not imported into SQLite.
- Existing tests for start/list/rename/kill/session cache pass, with new regression tests for duplicate PID/name preservation and concurrent writes.

## Constraints & Assumptions

- `@ai-devkit/agent-manager` already depends on `better-sqlite3`, so no new runtime storage dependency is required.
- CLI commands are local-only and synchronous registry operations are acceptable.
- The registry is process-local user data under `~/.ai-devkit`; no network or multi-user access is required.
- Live process identity is primarily `type + pid`; `sessionId` and `sessionFilePath` are metadata and may be empty early in startup.
- Generated names follow the adapter pattern `<project-folder>-<pid>` and should not replace a user-managed name when the same process already has one.

## Questions & Open Items

- Naming policy: preserve any existing name for a same `type + pid` row unless callers explicitly invoke `rename()` or `startAgent()` registers a managed name. Accepted assumption for this feature.
- Legacy cleanup: keep `agents.json` rather than deleting or rewriting it, but do not import it. Accepted assumption for rollback safety and stale-row prevention.
Loading
Loading