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
169 changes: 169 additions & 0 deletions docs/ai/design/2026-09-10-feature-herdr-runtime-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
---
phase: design
title: System Design & Architecture
description: Define the technical architecture, components, and data models
---

# System Design & Architecture

## Architecture Overview

Add a small runtime dispatch boundary around managed interactive agent operations. Keep agent adapters responsible for understanding agent tools such as Codex, Claude, Pi, Gemini, and Copilot. Keep runtime implementations responsible for where interactive processes live and how terminal IO/focus works.

```mermaid
graph TD
CLI[ai-devkit agent commands] --> Config[Runtime config resolver]
Config --> RuntimeFactory[Agent runtime factory]
RuntimeFactory --> TmuxRuntime[Tmux agent runtime]
RuntimeFactory --> HerdrRuntime[Herdr agent runtime]
CLI --> AgentService[agent.service managed operations]
AgentService --> RuntimeFactory
AgentService --> Registry[(agents registry DB)]
AgentService --> Adapters[Agent adapters]
TmuxRuntime --> Tmux[tmux CLI]
HerdrRuntime --> Herdr[Herdr CLI/API]
Adapters --> SessionFiles[Agent session files]
AgentService --> Channels[Channels and groups]
```

Responsibilities:

- Global config resolver: parse `~/.ai-devkit/.ai-devkit.json` runtime config once near command entry.
- Runtime factory: return a Herdr runtime only when the global provider is `herdr`; otherwise keep the existing tmux branch.
- Runtime implementation: start, send, read/wait, focus/open, stop for a terminal backend.
- Agent registry: persist AI DevKit names, type, cwd, session metadata, runtime provider, and opaque runtime ref.
- Agent adapters: detect and parse agent-specific process/session/transcript data.
- Herdr: own sessions, panes, PTYs, process IO, focus/open behavior, and lifecycle detection.

## Data Models

Config:

```json
// ~/.ai-devkit/.ai-devkit.json
{
"agentRuntime": {
"provider": "herdr"
}
}
```

Type model:

```ts
type AgentRuntimeProvider = 'tmux' | 'herdr';

interface RuntimeBackedRegistryFields {
runtime: AgentRuntimeProvider;
runtimeRef: unknown | null;
}
```

Registry migration:

```sql
ALTER TABLE agents ADD COLUMN runtime TEXT NOT NULL DEFAULT 'tmux';
ALTER TABLE agents ADD COLUMN runtime_ref TEXT NOT NULL DEFAULT '';
```

Compatibility:

- Treat `runtime_ref` as the canonical runtime handle for both tmux and Herdr.
- Keep the physical `tmux_session` column only as a legacy backfill/compatibility column.
- For tmux records, write `runtime_ref = { "session": "<name>" }` and mirror that session into `tmux_session` only for old readers.
- For Herdr records, write `runtime = 'herdr'` and Herdr's opaque ref JSON.
- When `runtime` is missing on old records, read as `tmux`.
- When tmux `runtime_ref` is empty, synthesize `{ "session": tmux_session }` in memory.

Example Herdr ref:

```json
{
"session": "default",
"paneId": "w1:p2",
"agentName": "reviewer"
}
```

## API Design

Internal Herdr runtime interfaces:

```ts
interface HerdrStartRuntime {
readonly provider: 'herdr';
isAvailable(): Promise<RuntimeAvailability>;
startAgent(input: RuntimeStartInput): Promise<RuntimeStartResult>;
}

interface HerdrInteractiveRuntime extends HerdrStartRuntime {
send(input: RuntimeSendInput): Promise<void>;
wait(input: RuntimeWaitInput): Promise<void>;
readOutput(input: RuntimeReadOutputInput): Promise<string>;
focus(input: RuntimeFocusInput): Promise<boolean>;
stop(input: RuntimeStopInput): Promise<void>;
}

type RuntimeAvailability =
| { ok: true; insideRuntime: boolean; currentPaneId?: string }
| { ok: false; reason: 'binary-missing' | 'backend-unreachable' | 'invalid-environment'; detail: string };
```

Herdr availability rules:

- If provider is `herdr`, require the `herdr` binary.
- Require Herdr backend/API reachability through `herdr api snapshot` or equivalent.
- Use `HERDR_ENV=1` and `HERDR_PANE_ID` only as context signals.
- Do not require the user to run `ai-devkit agent start` from inside Herdr.

Runtime dispatch:

- `agent start`: use configured global runtime.
- `agent send`, `agent send --wait`, focus/open, and kill: use the target registry row's stored runtime.
- Old registry rows without runtime use tmux.

## Component Breakdown

- `packages/cli/src/util/config.ts`: validate global `agentRuntime.provider` using provider values exported by `agent-manager`.
- `packages/cli/src/lib/GlobalConfig.ts`: read global runtime provider from `~/.ai-devkit/.ai-devkit.json`.
- `packages/cli/src/lib/Config.ts`: delegate runtime provider lookup to global config for caller compatibility.
- `packages/cli/src/services/agent/agent.service.ts`: keep managed-agent command orchestration while consuming runtime interfaces from `agent-manager`.
- `packages/agent-manager/src/terminal/TmuxManager.ts`: keep as low-level tmux helper.
- `packages/agent-manager/src/runtime/AgentRuntime.ts`: centralize runtime interfaces, Herdr runtime construction, and registry-entry runtime predicates.
- `packages/agent-manager/src/runtime/HerdrAgentRuntime.ts`: add injectable Herdr CLI client and typed parsing/errors.
- `packages/agent-manager/src/utils/AgentRegistry.ts`: persist runtime/runtimeRef with old-row defaults.
- `packages/agent-manager/src/database/migrations/005_agent_runtime.sql`: additive migration.
- Herdr CLI client: injectable exec dependency, JSON parsing, typed errors.

## Design Decisions

- Use `runtimeRef` in active code paths. The legacy `tmux_session` DB column remains only to backfill old rows and avoid a destructive SQLite migration in this feature branch.
- Store Herdr refs in `runtime_ref`, not a new DB. AI DevKit already owns agent registry records; a second DB would duplicate identity and lifecycle state.
- Use stored runtime for existing agents. A target agent should be operated through the backend that owns its live session, regardless of current config.
- Do not mirror Herdr details into `agent detail`. AI DevKit detail should show AI DevKit assignment/session metadata, with a compact runtime reference/debug field if needed.
- Use Herdr CLI/API initially. Socket API can replace the transport later behind the same runtime interface.
- Treat Herdr IDs as opaque. The adapter captures returned IDs from Herdr responses and never guesses or derives them.

## Non-Functional Requirements

Reliability:

- Runtime availability errors should be specific and actionable.
- Malformed `runtime_ref` should not crash unrelated agent list operations; report target-specific failures for operations that need the ref.
- Existing tmux flows must keep passing tests.

Performance:

- CLI-based Herdr operations are acceptable for MVP.
- Runtime interface should allow a future socket/event-backed Herdr client without changing command semantics.

Security:

- Do not execute shell strings through a shell when calling Herdr; use argument arrays.
- Do not store secrets in `runtime_ref`.
- Treat Herdr response JSON as untrusted input and validate required fields.

Compatibility:

- Old records and current docs/tests should remain valid during the migration.
- User-facing messages should stop hardcoding "tmux" where the operation is now runtime-generic.
135 changes: 135 additions & 0 deletions docs/ai/implementation/2026-09-10-feature-herdr-runtime-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
phase: implementation
title: Implementation Guide
description: Technical implementation notes, patterns, and code guidelines
---

# Implementation Guide

## Development Setup

- Work in `.worktrees/feature-herdr-runtime-integration` on branch `feature-herdr-runtime-integration`.
- Use the existing npm workspace commands and focused Vitest targets for `packages/cli` and `packages/agent-manager`.
- Herdr command contracts must be confirmed before real integration code is finalized. Until then, inject a Herdr command runner so tests can use fake JSON responses.

## Code Structure

- `packages/cli/src/util/config.ts`: shared runtime provider validation.
- `packages/cli/src/lib/GlobalConfig.ts`: runtime config owner and global provider lookup.
- `packages/cli/src/lib/Config.ts`: global runtime provider delegation for caller compatibility.
- `packages/cli/src/services/agent/agent.service.ts`: managed agent orchestration that consumes runtime contracts from `agent-manager`.
- `packages/cli/src/commands/agent.ts`: command-level config reading, UI rendering, and runtime factory usage.
- `packages/agent-manager/src/utils/AgentRegistry.ts`: runtime metadata persistence.
- `packages/agent-manager/src/database/migrations/005_agent_runtime.sql`: additive registry migration.
- `packages/agent-manager/src/terminal/TmuxManager.ts`: low-level tmux operations.
- `packages/agent-manager/src/runtime/AgentRuntime.ts`: runtime interfaces, Herdr runtime factory, and Herdr registry-entry predicate.
- `packages/agent-manager/src/runtime/HerdrAgentRuntime.ts`: injected Herdr CLI client used by the CLI and reusable by later console/channel code.

## Implementation Notes

### Core Features

- Runtime config:
- Parse only `agentRuntime.provider` from global `~/.ai-devkit/.ai-devkit.json`.
- Ignore project-level `agentRuntime` in project install config.
- Default to `tmux` for missing config, missing `agentRuntime`, or missing provider.
- Reject unknown providers with valid values.
- Registry migration:
- Add `runtime` and `runtime_ref`.
- Keep the physical `tmux_session` column only for legacy row backfill and old-reader compatibility.
- Parse `runtime_ref` as opaque JSON for known providers.
- Preserve old rows as tmux.
- Runtime dispatch:
- Start uses configured runtime.
- Send/focus/kill use stored runtime on the target row.
- Managed start/stop/focus/send orchestration lives in `agent-manager`; CLI passes the selected runtime provider and renders results.
- Runtime contracts, factories, and runtime-entry predicates are exported from `agent-manager`; CLI does not define Herdr runtime interfaces or construct the Herdr adapter class directly.
- Herdr references are captured from Herdr responses only.
- Herdr availability:
- For `provider = herdr`, require Herdr binary availability and backend/API reachability.
- Treat `HERDR_ENV` and `HERDR_PANE_ID` as optional context signals.
- Cleanup pass:
- Removed an unused Herdr availability error reason.
- Simplified Herdr runtime ref parsing to normalize each field once.
- Trimmed duplicate runtime-provider tests from `ConfigManager`; `GlobalConfigManager` and config util tests own those behavior cases.
- Refactor pass:
- Moved runtime start/stop/focus/send orchestration from CLI agent service into `agent-manager`.
- Moved corresponding start/stop/focus/send tests into agent-manager runtime tests.
- Kept CLI responsible for option parsing, config lookup, UI messages, and send/wait output formatting.

### Implemented Behavior

- Global config accepts only `tmux` and `herdr`; missing config, missing `agentRuntime`, and missing provider resolve to `tmux`.
- Project `.ai-devkit.json` does not configure runtime selection.
- Existing registry rows without runtime columns are migrated/read as tmux-backed records.
- Registry entries expose `runtime` and opaque JSON `runtimeRef`; tmux rows use `{ session: "<tmux-session>" }`.
- Old SQLite rows with only `tmux_session` synthesize `{ session: tmux_session }` when read.
- `agent start` reads global runtime config once for interactive mode and starts through tmux or Herdr accordingly.
- `agent send`, `agent send --wait`, `agent open`, and `agent kill` use stored Herdr refs when the target registry row is Herdr-backed.
- Runtime-aware start, stop, focus, and prompt delivery are centralized in `packages/agent-manager/src/runtime/ManagedAgentRuntime.ts`; `agent send --wait` reads AI DevKit session transcripts for both tmux and Herdr-backed agents.
- `agent send --wait` waits through a short transcript-flush grace period before reporting that an agent returned to waiting without assistant output.
- Durable mode is unchanged and remains separate from interactive runtime selection.

### Patterns & Best Practices

- Use `execFile` or injected argument-array command runners, not shell string execution.
- Validate Herdr JSON response shape before storing refs.
- Keep errors domain-specific enough for CLI messages and tests.
- Do not leak Herdr's full session model into AI DevKit detail/list data structures.
- Active call sites should consume `runtimeRef`; do not add new `tmuxSession` reads.

## Integration Points

- Herdr CLI/API:
- availability: `herdr --version` or equivalent plus `herdr api snapshot` or equivalent.
- start: command returns session, pane ID, agent name, and optional PID.
- send: command targets a returned pane/session ref.
- read/wait: command returns output/result where available; otherwise AI DevKit transcript polling remains the result source.
- focus/open: command targets returned pane/session ref.
- stop: command delegates lifecycle cleanup to Herdr where supported.
- Agent adapters:
- Continue parsing agent-specific session files and summaries.
- Do not become Herdr-aware unless Herdr needs adapter-specific launch metadata.

## Error Handling

- Unknown runtime provider: fail before side effects.
- Herdr binary missing: fail before start/send with clear installation/PATH guidance.
- Herdr backend unreachable: fail before start/send with backend/API guidance.
- Missing required Herdr IDs in response: fail and do not register the agent.
- PID unavailable: handle explicitly; do not guess.
- Stale runtime ref: fail target operation and suggest focusing/listing/restarting the agent.
- Malformed registry runtime ref: treat as target-specific data corruption, not a global list crash.

## Performance Considerations

- CLI process startup overhead is acceptable for MVP.
- Avoid repeated runtime config reads inside loops or per-target group sends.
- Keep the runtime boundary transport-agnostic so Herdr socket/event support can replace CLI calls later.

## Security Notes

- Do not pass prompts through shell interpolation.
- Do not store secrets in runtime refs or debug logs.
- Treat Herdr response JSON as untrusted.
- Preserve existing AI DevKit ownership over channel permissions, assignments, and validation evidence.

## Validation Evidence

- `packages/cli`: `npx vitest run src/__tests__/util/config.test.ts src/__tests__/lib/Config.test.ts src/__tests__/lib/GlobalConfig.test.ts src/__tests__/services/agent/agent.service.test.ts src/__tests__/commands/agent.test.ts`
- `packages/cli`: `npx tsc --noEmit`
- `packages/agent-manager`: `npx vitest run src/__tests__/utils/AgentRegistry.test.ts src/__tests__/runtime/AgentRuntime.test.ts src/__tests__/runtime/HerdrAgentRuntime.test.ts`
- `packages/agent-manager`: `npm run typecheck`
- `packages/agent-manager`: `npm run build`
- repository: `npx ai-devkit@latest lint --feature herdr-runtime-integration`
- repository: `git diff --check`
- Refactor validation:
- `packages/agent-manager`: `npx vitest run src/__tests__/runtime/ManagedAgentRuntime.test.ts src/__tests__/runtime/AgentRuntime.test.ts src/__tests__/runtime/HerdrAgentRuntime.test.ts`
- `packages/agent-manager`: `npm run typecheck`
- `packages/agent-manager`: `npm run lint`
- `packages/agent-manager`: `npm run build`
- `packages/cli`: `npx vitest run src/__tests__/services/agent/agent.service.test.ts src/__tests__/commands/agent.test.ts src/__tests__/services/plugin/plugin-loader.service.test.ts`
- `packages/cli`: `npx tsc --noEmit`
- `packages/cli`: `npm run lint`
- repository: `npx ai-devkit@latest lint --feature herdr-runtime-integration`
- repository: `git diff --check`
Loading
Loading