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
177 changes: 0 additions & 177 deletions .agents/sow/current/SOW-0005-20260526-opencode-adapter.md

This file was deleted.

341 changes: 341 additions & 0 deletions .agents/sow/done/SOW-0005-20260526-opencode-adapter.md

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions .agents/sow/pending/SOW-0023-20260530-session-provider-carrier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# SOW-0023 - session provider carrier (populate sessions.provider / provider_alias)

## Status

Status: open

Sub-state: proposed follow-up, awaiting operator prioritization. Discovered during SOW-0005 (opencode adapter) round-1 external review (codex P2.3). Not blocking SOW-0005 — the op-scoped provider/alias is complete and authoritative; this is a denormalized session-level convenience.

## Requirements

### Purpose

Make `sessions.provider` and `sessions.provider_alias` reachable. `data-model.md` §sessions defines both columns (`provider TEXT`, `provider_alias TEXT -- user-defined provider alias (opencode); NULL otherwise`), but **no canonical session event carries provider fields**: `SessionStartedEvent` (`internal/canonical/events.go`) carries `Model` only — no `Provider`/`ProviderAlias` — and the ingest writer's `sessions` upsert (`internal/ingest/writer.go`) never writes the `provider`/`provider_alias` columns (they are written only for `ops`). So the session-level provider columns the data-model promises are structurally unreachable from any adapter. This SOW adds a session provider carrier to the canonical event model + ingest writer so adapters can populate the session-level provider columns, and wires the opencode adapter (which already knows the per-message provider) to set them.

### User Request

Implied by `data-model.md` §sessions (the columns exist) and the opencode adapter's multi-provider awareness. SOW-0005 round-1 review surfaced that the home is unwired.

### Assistant Understanding

Facts:

- `internal/canonical/events.go`: `SessionStartedEvent` = {EventBase, SessionNativeID, ParentNativeID, RootNativeID, Kind, AgentName, Model, Cwd, CallPath, Extras, ...} — no `Provider`/`ProviderAlias`. `OpStartedEvent` carries `Provider`/`ProviderAlias`.
- `internal/ingest/writer.go`: the `sessions` UPSERT maps `kind, agent_name, model, cwd, call_path, status, ...`; it does NOT reference `provider`/`provider_alias` (those are in the `ops` UPSERT only).
- `data-model.md` §sessions: `provider`, `provider_alias` columns are defined ("primary/last-known"; alias is opencode-specific).
- The opencode mapper has the per-message `providerID` (alias) and a best-effort canonical mapping; it currently surfaces them per-op + in SessionStarted `Extras.providerID` only.

Inferences:

- Cleanest carrier: add `Provider` + `ProviderAlias` (+ optionally `Model` already present) to `SessionStartedEvent` (and/or a `SessionUpdatedEvent` for last-known refresh), mirroring how ops carry them; the writer marshals them into the `sessions` columns with the same `COALESCE(NULLIF(excluded.x,''), sessions.x)` idempotency discipline used elsewhere.
- A single session-level alias is inherently lossy for **multi-provider** sessions (opencode sessions can span providers). Decide in the gate: "primary = last-known" vs "first" vs leave NULL when >1 distinct provider. The op-scoped data remains the authoritative complete record; this column is a UI convenience.
- Shared infrastructure: claude-code/codex/opencode could all populate it. Deliberately out of SOW-0005's adapter-additive blast radius.

Unknowns:

- Whether the session row should carry "primary" provider (last-known) or be NULL for genuinely multi-provider sessions. Resolve in the gate against the presenter's intended use.
- Re-emit/idempotency: SessionStarted may re-emit (tailer full-tree re-feed); the writer must not corrupt the column. Confirm against the idempotent-write model.

### Acceptance Criteria

1. `SessionStartedEvent` (and/or `SessionUpdatedEvent`) carries `Provider` + `ProviderAlias`; `internal/canonical` tests cover it. **Verification**: `go test` for canonical.
2. The ingest writer marshals session `Provider`/`ProviderAlias` into `sessions.provider`/`sessions.provider_alias`, idempotently under re-emit. **Verification**: an ingester test asserts a `SessionStartedEvent{Provider:...,ProviderAlias:...}` lands in the columns and a re-emit does not corrupt them.
3. The opencode adapter populates the session provider columns from its per-message provider (per the gate's primary-provider decision). **Verification**: an opencode golden/invariant test asserts the session event's provider fields; the `c_multi_provider` fixture exercises the multi-provider decision.
4. Specs reconciled: SOW-0005 AC#7 note resolved; `data-model.md` + `canonical-events.md` describe the session provider carrier. **Verification**: spec-drift sweep clean.

## Analysis

Sources checked: `internal/canonical/events.go`, `internal/ingest/writer.go`, `.agents/sow/specs/{data-model.md,canonical-events.md,adapter-opencode.md}`, `internal/adapters/opencode/mapper*.go`. Discovered 2026-05-30 during SOW-0005 round-1 review.

Risks:

- **R1 — Shared-surface change.** Touches `internal/canonical` + `internal/ingest` (every adapter). Mitigation: additive field (no existing adapter sets it → no behavior change until opt-in); full gate + external review.
- **R2 — Multi-provider lossiness.** A single session alias cannot represent a multi-provider session. Mitigation: the gate's primary-vs-NULL decision; the op-scoped data stays authoritative.
- **R3 — Idempotency.** Re-emitted SessionStarted must not corrupt the column. Mitigation: COALESCE/NULLIF write + a re-emit test.

## Pre-Implementation Gate

(To be filled by the assistant picking this SOW up. Required before moving to `current/`.)

## Implementation

(Empty placeholder.)

## Validation

(Empty placeholder.)

## Reviews

(Empty placeholder.)

## Outcome

Pending.

## Lessons / Follow-Ups

Pending. Related: [[SOW-0021-20260530-turn-extras-carrier]] (the same class of canonical-carrier gap, for turns).
76 changes: 76 additions & 0 deletions .agents/sow/pending/SOW-0024-20260530-per-source-counts-health.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# SOW-0024 - per-source row counts in /api/health

## Status

Status: open

Sub-state: proposed follow-up, awaiting operator prioritization. Discovered during SOW-0005 (opencode adapter) round-1 external review (codex P2.2). Not blocking SOW-0005 — the opencode probe LOGS its counts at startup and registers the source (visible in `/api/sources`); this SOW generalizes the richer per-source metadata into `/api/health`.

## Requirements

### Purpose

Surface per-source content metadata (e.g. session/message/part counts, latest schema/migration marker) in `/api/health` (and/or `/api/sources`) as a GENERAL, all-adapter feature. Today the opencode auto-discovery probe computes `(session_count, message_count, part_count, latest_migration)` and writes them to the startup log only; SOW-0005 AC#8 originally asked for them in `/api/health`, but a bespoke opencode-only health field does not generalize and would special-case the presenter. This SOW designs a generic source-metadata surface so every adapter can contribute health-relevant counts without per-adapter presenter branches.

### User Request

Implied by SOW-0005 AC#8 ("exposes (session_count, message_count, part_count, latest_migration_name) in /api/health") — amended during SOW-0005 to log-only + this follow-up, because the full surface is cross-cutting and should be general.

### Assistant Understanding

Facts:

- `internal/presenter/health.go` builds `/api/health` from a `sources` query + a parse-error rollup; it has no per-source content-count field.
- `cmd/ai-viewer-ingest/discovery.go` + `internal/adapters/opencode/migrations.go:ProbeStatus` compute opencode counts at startup and LOG them; they are not persisted into ai-viewer's DB for the presenter to read.
- `source_progress` / `sources` tables (`data-model.md`) hold per-source state (cursor, last_seq, last_ts, parse_errors). There is no general per-source "content summary" column.
- File-based adapters (aiagent/claude-code/codex) have no cheap O(1) row count analogous to opencode's `COUNT(*)`; a generalized surface must tolerate "count unknown/not-applicable".

Inferences:

- A general design: a small per-source metadata blob (JSON) the adapter/probe can populate (e.g. `sources.meta_json` or `source_progress.extras`), surfaced verbatim under each source in `/api/health` (or `/api/sources`). Adapters that have cheap counts populate them; others omit. Avoids per-adapter presenter branches.
- Alternatively, a periodic ingester-side rollup of canonical row counts per source (sessions/turns/ops the ingester already wrote) — which is adapter-agnostic and always available — may be more useful than source-native counts. Decide in the gate (source-native probe counts vs ingested-canonical counts).

Unknowns:

- Which counts are actually useful for health triage (source-native vs ingested-canonical), and whether they belong in `/api/health` (triage) or `/api/sources` (inventory). Resolve in the gate with the presenter spec.
- Staleness: probe counts are point-in-time at startup; ingested counts are live. The gate picks the model + documents freshness.

### Acceptance Criteria

1. A general per-source metadata surface exists (schema + writer + presenter) that any adapter can populate without a presenter code branch. **Verification**: presenter test asserts a source's metadata round-trips into `/api/health` (or `/api/sources`).
2. The opencode source surfaces its `(session/message/part counts, latest_migration)`; file-based adapters omit gracefully (no error, no zero-as-real). **Verification**: an integration test with an opencode fixture + a file-based fixture asserts the opencode metadata appears and the file-based one is absent/omitted.
3. Specs reconciled: SOW-0005 AC#8 amendment resolved; `data-model.md` + `observability.md`/`rest-api.md` describe the surface. **Verification**: spec-drift sweep clean.

## Analysis

Sources checked: `internal/presenter/health.go`, `cmd/ai-viewer-ingest/discovery.go`, `internal/adapters/opencode/migrations.go`, `.agents/sow/specs/{data-model.md,observability.md,rest-api.md}`. Discovered 2026-05-30 during SOW-0005 round-1 review.

Risks:

- **R1 — Cross-cutting surface.** Touches schema + ingester + presenter. Mitigation: additive (new optional metadata; no existing field changes); full gate + external review.
- **R2 — Generalization.** Must not special-case opencode in the presenter. Mitigation: the metadata blob/rollup is adapter-agnostic; adapters opt in.
- **R3 — Freshness semantics.** Probe-time vs live counts. Mitigation: the gate decides + documents which, and `/api/health` labels it.

## Pre-Implementation Gate

(To be filled by the assistant picking this SOW up. Required before moving to `current/`.)

## Implementation

(Empty placeholder.)

## Validation

(Empty placeholder.)

## Reviews

(Empty placeholder.)

## Outcome

Pending.

## Lessons / Follow-Ups

Pending.
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# SOW-0025 - canonical attachment PayloadKind (file/user attachments)

## Status

Status: open

Sub-state: proposed follow-up, awaiting operator prioritization. Discovered during SOW-0005 (opencode adapter) round-4 external review (codex P2-3). Not blocking SOW-0005 — opencode ships an interim no-loss representation (an INF LogEntry carrying the attachment metadata).

## Requirements

### Purpose

Give user/file attachments a first-class canonical representation. The canonical `PayloadRefEvent.PayloadKind` set is `llm_request | llm_response | llm_sdk_request | llm_sdk_response | llm_reasoning | tool_request | tool_response | log` (`internal/canonical/events.go`). None represents a USER file attachment (an image/file a user attached to a turn). opencode's `file` part carries exactly that (filename/url/mime). SOW-0005 round-4 surfaced that the adapter was emitting a non-canonical `PayloadKind: "user_attachment"` — a contract violation — and fixed it by emitting an INF `LogEntryEvent` with the attachment metadata in extras instead (no data loss, canonical-clean, but not a first-class payload servable via the future `/api/payloads`). This SOW adds a canonical attachment payload kind so file attachments across adapters (opencode today; codex/claude-code likely have analogues) are a first-class, servable payload.

### User Request

Implied by the data model (payloads are first-class) + opencode's file parts. SOW-0005 round-4 review flagged the canonical-contract gap.

### Assistant Understanding

Facts:

- `internal/canonical/events.go`: `PayloadRefEvent.PayloadKind` documents 8 kinds; none is a user/file attachment.
- `internal/adapters/opencode/mapper_emitters.go`: a `file` part now emits an INF `LogEntryEvent` ("file attachment", extras `{filename,url,mime}`) — the SOW-0005 round-4 interim (was a non-canonical `user_attachment` PayloadRef).
- The `/api/payloads` serving route is itself Phase 2 (unregistered today), so the interim LogEntry loses no currently-served capability.

Inferences:

- Cleanest carrier: add a canonical `user_attachment` (or `attachment`) value to the `PayloadRefEvent.PayloadKind` set, document it in `canonical-events.md` + `data-model.md`, ensure the ingest writer accepts it (payload_refs.kind is TEXT — likely no enum constraint, confirm), and the presenter/UI renders it. Then opencode's `file` part emits a PayloadRef of that kind (LocationURI = the file url / `opencode-sqlite://` ref) instead of (or in addition to) the LogEntry.
- This is a shared-surface change (canonical + ingest + presenter + every adapter that has attachments) — deliberately out of SOW-0005's adapter-additive scope.

Unknowns:

- Whether `/api/payloads` (Phase 2) should land first so the attachment payload is actually servable, or whether the kind can be added ahead of the serving route. Sequence in the gate.
- Whether codex/claude-code/ai-agent have attachment analogues to map at the same time (cross-adapter consistency).

### Acceptance Criteria

1. The canonical `PayloadRefEvent.PayloadKind` set includes an attachment kind; `internal/canonical` + `data-model.md` + `canonical-events.md` document it. **Verification**: `go test` for canonical; spec-drift sweep clean.
2. The ingest writer persists the new kind into `payload_refs.kind`; a presenter/UI surface renders it (or it is explicitly deferred with the kind reserved). **Verification**: ingester + presenter tests.
3. The opencode `file` part emits a PayloadRef of the attachment kind (replacing or complementing the interim LogEntry); golden updated. **Verification**: an opencode golden with a file part asserts the canonical attachment PayloadRef.
4. Specs reconciled: adapter-opencode.md file-part mapping updated; SOW-0005 round-4 interim note resolved. **Verification**: spec-drift sweep clean.

## Analysis

Sources checked: `internal/canonical/events.go`, `internal/ingest/writer.go` (payload_refs upsert), `internal/adapters/opencode/mapper_emitters.go`, `.agents/sow/specs/{canonical-events.md,data-model.md,adapter-opencode.md}`. Discovered 2026-05-31 during SOW-0005 round-4 review.

Risks:

- **R1 — Shared-surface change.** Touches canonical + ingest + presenter + adapters. Mitigation: additive enum value (no existing kind changes); full gate + external review.
- **R2 — Serving route ordering.** `/api/payloads` is Phase 2; the kind may land before it is servable. Mitigation: the gate sequences this; the kind is reserved + rendered even if served later.

## Pre-Implementation Gate

(To be filled by the assistant picking this SOW up. Required before moving to `current/`.)

## Implementation

(Empty placeholder.)

## Validation

(Empty placeholder.)

## Reviews

(Empty placeholder.)

## Outcome

Pending.

## Lessons / Follow-Ups

Pending. Related canonical-carrier-gap follow-ups: SOW-0021 (turn extras), SOW-0023 (session provider), SOW-0024 (per-source health counts).
Loading