generated from coulomb/repo-seed
Compare commits
14 Commits
4f28cd67cf
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 43bea485aa | |||
| 63eb431db9 | |||
| 3250a1746f | |||
| 41bfb6e0f3 | |||
| d2e50cf96a | |||
| 01d2affc3b | |||
| 292b656952 | |||
| 0a5ba5c24a | |||
| a66d502b95 | |||
| f9f91a0ca8 | |||
| 06bcfdc1d9 | |||
| e237dcc622 | |||
| 0d05dfcc5d | |||
| 15ba625351 |
20
.claude/rules/agents.md
Normal file
20
.claude/rules/agents.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
## Kaizen Agents
|
||||||
|
|
||||||
|
Specialized agent personas available on demand via the state-hub MCP.
|
||||||
|
|
||||||
|
**Discover:** `list_kaizen_agents()` — returns all agents with name, description, category
|
||||||
|
**Load:** `get_kaizen_agent("tdd-workflow")` — returns full instructions; read and follow them
|
||||||
|
|
||||||
|
Common agents:
|
||||||
|
|
||||||
|
| Agent | Category | When to use |
|
||||||
|
|-------|----------|-------------|
|
||||||
|
| `tdd-workflow` | testing | Step-by-step TDD8 workflow for any feature |
|
||||||
|
| `code-refactoring` | quality | Code quality analysis and safe refactoring |
|
||||||
|
| `test-maintenance` | testing | Diagnose and fix failing tests |
|
||||||
|
| `requirements-engineering` | process | Prevent interface/mock mismatches upfront |
|
||||||
|
| `keepaTodofile` | process | Maintain TODO.md during work |
|
||||||
|
| `project-management` | process | Track status, determine next steps |
|
||||||
|
| `datamodel-optimization` | quality | Optimize dataclasses and data structures |
|
||||||
|
|
||||||
|
All 17 agents: call `list_kaizen_agents()` for the full list.
|
||||||
8
.claude/rules/architecture.md
Normal file
8
.claude/rules/architecture.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## Architecture
|
||||||
|
|
||||||
|
<!-- TODO: Describe the key design decisions and component structure.
|
||||||
|
Key modules, data flows, external integrations, state machines, etc. -->
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
`~/state-hub/mcp_server/TOOLS.md` — MCP tool reference
|
||||||
50
.claude/rules/credential-routing.md
Normal file
50
.claude/rules/credential-routing.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Credential and access routing
|
||||||
|
|
||||||
|
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||||
|
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||||
|
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||||
|
|
||||||
|
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||||
|
other credential need belongs to another subsystem. **Do not** message
|
||||||
|
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||||
|
|
||||||
|
### Lookup (do this first)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
warden route find "<describe your need>" --json
|
||||||
|
warden route show <catalog-id> --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||||
|
|
||||||
|
| Agent runtime | How to orient |
|
||||||
|
| --- | --- |
|
||||||
|
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=agentic-resources` is for coordination, not secret vending |
|
||||||
|
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
|
||||||
|
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||||
|
|
||||||
|
### Quick routing table
|
||||||
|
|
||||||
|
| I need… | Owner | ops-warden executes? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||||
|
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||||
|
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||||
|
| Authorization decision | flex-auth | No — route only |
|
||||||
|
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||||
|
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||||
|
|
||||||
|
### Anti-patterns (do not do these)
|
||||||
|
|
||||||
|
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||||
|
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||||
|
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||||
|
|
||||||
|
### Other capabilities (reuse-surface)
|
||||||
|
|
||||||
|
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||||
|
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||||
|
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||||
|
get wrong.
|
||||||
|
|
||||||
|
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||||
38
.claude/rules/first-session.md
Normal file
38
.claude/rules/first-session.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
## First Session Protocol
|
||||||
|
|
||||||
|
Triggered when `get_domain_summary("infotech")` shows **no workstreams**.
|
||||||
|
The project is registered but work has not yet been structured.
|
||||||
|
|
||||||
|
**Step 1 — Read, don't write**
|
||||||
|
- `~/the-custodian/canon/projects/infotech/project_charter_v0.1.md` — purpose, scope
|
||||||
|
- `~/the-custodian/canon/projects/infotech/roadmap_v0.1.md` — planned phases
|
||||||
|
- Scan repo root: README, directory structure, existing code or docs
|
||||||
|
|
||||||
|
**Step 2 — Survey in-progress work**
|
||||||
|
Look for TODOs, open branches, half-finished files. Note done vs. started but incomplete.
|
||||||
|
|
||||||
|
**Step 3 — Propose workstreams to Bernd**
|
||||||
|
Propose 1–3 workstreams — each a coherent strand, weeks to months, anchored to a
|
||||||
|
roadmap phase. **Wait for approval before creating.**
|
||||||
|
|
||||||
|
**Step 4 — Create workplan file first, then DB record (ADR-001)**
|
||||||
|
```
|
||||||
|
workplans/AGENTIC-WP-NNNN-<slug>.md ← write this first
|
||||||
|
```
|
||||||
|
Then register in the hub:
|
||||||
|
```
|
||||||
|
create_workstream(topic_id="f39fa2a3-c491-414c-a91b-b4c5fcc6139c", title="...", owner="...", description="...")
|
||||||
|
create_task(workstream_id="<id>", title="...", priority="high|medium|low")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 5 — Record the setup**
|
||||||
|
```
|
||||||
|
add_progress_event(
|
||||||
|
summary="First session: structured infotech into N workstreams, M tasks",
|
||||||
|
event_type="milestone",
|
||||||
|
topic_id="f39fa2a3-c491-414c-a91b-b4c5fcc6139c",
|
||||||
|
detail={"workstreams": [...], "tasks_created": M}
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
<!-- Delete or archive this file once past first session -->
|
||||||
8
.claude/rules/repo-boundary.md
Normal file
8
.claude/rules/repo-boundary.md
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
## Repo boundary
|
||||||
|
|
||||||
|
This repo owns **agentic-resources** only. It does not own:
|
||||||
|
|
||||||
|
<!-- TODO: List what belongs in adjacent repos, e.g.:
|
||||||
|
- SSH key management → railiance-infra/
|
||||||
|
- State hub code → state-hub/
|
||||||
|
-->
|
||||||
5
.claude/rules/repo-identity.md
Normal file
5
.claude/rules/repo-identity.md
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
**Purpose:** Iterating towards optimal agentic performance.
|
||||||
|
|
||||||
|
**Domain:** infotech
|
||||||
|
**Repo slug:** agentic-resources
|
||||||
|
**Topic ID:** f39fa2a3-c491-414c-a91b-b4c5fcc6139c
|
||||||
85
.claude/rules/session-protocol.md
Normal file
85
.claude/rules/session-protocol.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
## Session Protocol
|
||||||
|
|
||||||
|
Dev Hub (State Hub API): http://127.0.0.1:8000
|
||||||
|
MCP server name in `~/.claude.json`: `dev-hub`
|
||||||
|
|
||||||
|
**Step 1 — Orient**
|
||||||
|
|
||||||
|
Read the offline-safe brief first — it works without a live hub connection:
|
||||||
|
```bash
|
||||||
|
cat .custodian-brief.md
|
||||||
|
```
|
||||||
|
Then call the MCP tool for richer cross-domain context when MCP tools are exposed:
|
||||||
|
```
|
||||||
|
get_domain_summary("infotech")
|
||||||
|
```
|
||||||
|
If MCP tools are unavailable in the current agent session, use the REST API:
|
||||||
|
```bash
|
||||||
|
curl -s "http://127.0.0.1:8000/state/summary" | python3 -m json.tool
|
||||||
|
```
|
||||||
|
If the hub is offline: `cd ~/state-hub && make api`
|
||||||
|
|
||||||
|
**Step 2 — Check inbox**
|
||||||
|
With MCP tools:
|
||||||
|
```
|
||||||
|
get_messages(to_agent="agentic-resources", unread_only=True)
|
||||||
|
```
|
||||||
|
Mark read with `mark_message_read(message_id)`. Reply or act on coordination
|
||||||
|
requests before proceeding.
|
||||||
|
|
||||||
|
Without MCP tools:
|
||||||
|
```bash
|
||||||
|
curl -s "http://127.0.0.1:8000/messages/?to_agent=agentic-resources&unread_only=true" \
|
||||||
|
| python3 -m json.tool
|
||||||
|
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||||
|
-H "Content-Type: application/json" -d '{}'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3 — Scan workplans**
|
||||||
|
```bash
|
||||||
|
ls workplans/
|
||||||
|
```
|
||||||
|
For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||||
|
`wait`/`todo`/`progress` tasks.
|
||||||
|
|
||||||
|
**Step 4 — Present brief**
|
||||||
|
|
||||||
|
1. **Active workstreams** for `infotech` — title, task counts, blocking decisions
|
||||||
|
2. **Pending tasks** from `workplans/` + any `[repo:agentic-resources]` hub tasks
|
||||||
|
3. **Goal guidance** — if `goal_guidance` in summary:
|
||||||
|
- `needs_workplan`: surface as top action — *"Repo goal '{title}' has no workplan yet"*
|
||||||
|
- `alignment_warnings`: flag if active work is not aligned with current goal
|
||||||
|
4. **Suggested next action** — highest-priority open item
|
||||||
|
5. **SBOM status** — flag if `last_sbom_at` is unset for this repo
|
||||||
|
|
||||||
|
If no workstreams: follow First Session Protocol (`first-session.md`).
|
||||||
|
|
||||||
|
**During work:** `record_decision()` · `add_progress_event()` · `resolve_decision()`
|
||||||
|
|
||||||
|
> State Hub is a *read model*. Bootstrap tools (`create_workstream`, `create_task`)
|
||||||
|
> are First Session Protocol only. Work structure belongs in repo files (ADR-001).
|
||||||
|
|
||||||
|
**Session close:**
|
||||||
|
With MCP tools:
|
||||||
|
```
|
||||||
|
add_progress_event(summary="...", topic_id="f39fa2a3-c491-414c-a91b-b4c5fcc6139c", workstream_id="<uuid>")
|
||||||
|
```
|
||||||
|
Without MCP tools:
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{"topic_id":"f39fa2a3-c491-414c-a91b-b4c5fcc6139c","workstream_id":"<uuid>","event_type":"note","summary":"what changed","author":"codex"}'
|
||||||
|
```
|
||||||
|
If workplan files were modified, ensure the local copy is up to date first:
|
||||||
|
```bash
|
||||||
|
git -C <repo_path> pull --ff-only
|
||||||
|
cd ~/state-hub && make fix-consistency REPO=agentic-resources
|
||||||
|
```
|
||||||
|
For repos where implementation runs on a remote machine (e.g. CoulombCore),
|
||||||
|
use the combined target which pulls before fixing:
|
||||||
|
```bash
|
||||||
|
cd ~/state-hub && make fix-consistency-remote REPO=agentic-resources
|
||||||
|
```
|
||||||
|
**C-15** (DB task ahead of file) is normal in multi-machine workflows — writeback
|
||||||
|
will sync the file to match DB. **C-16** (repo behind remote) blocks all writes
|
||||||
|
until you pull — intentional to prevent clobbering remote progress.
|
||||||
19
.claude/rules/stack-and-commands.md
Normal file
19
.claude/rules/stack-and-commands.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
## Stack
|
||||||
|
|
||||||
|
<!-- TODO: Fill in language, frameworks, and key dependencies -->
|
||||||
|
- **Language:**
|
||||||
|
- **Key deps:**
|
||||||
|
|
||||||
|
## Dev Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# TODO: Fill in the standard commands for this repo
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
|
||||||
|
# Lint / type check
|
||||||
|
|
||||||
|
# Build / package (if applicable)
|
||||||
|
```
|
||||||
40
.claude/rules/workplan-convention.md
Normal file
40
.claude/rules/workplan-convention.md
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
|
File location: `workplans/AGENTIC-WP-NNNN-<slug>.md`
|
||||||
|
ID prefix: `AGENTIC-WP-`
|
||||||
|
|
||||||
|
Work items originate as files in this repo **before** being registered in the hub.
|
||||||
|
|
||||||
|
Canonical workplan/workstream frontmatter statuses are:
|
||||||
|
`proposed`, `ready`, `active`, `blocked`, `backlog`, `finished`, `archived`.
|
||||||
|
Use `proposed` for a newly drafted plan, `ready` after review against current
|
||||||
|
repo state, and `finished` when implementation is complete. `stalled` and
|
||||||
|
`needs_review` are derived health labels, not stored statuses.
|
||||||
|
|
||||||
|
Closed workplans may be moved to `workplans/archived/` with a completion-date
|
||||||
|
prefix: `YYMMDD-AGENTIC-WP-NNNN-<slug>.md`. The frontmatter id remains
|
||||||
|
unchanged; the prefix is only for quick visual reference.
|
||||||
|
|
||||||
|
Small opportunistic tasks discovered during another session use **Ad Hoc Tasks**:
|
||||||
|
`workplans/ADHOC-YYYY-MM-DD.md`, workstream slug `adhoc-YYYY-MM-DD`, and task ids
|
||||||
|
`ADHOC-YYYY-MM-DD-T01`, `T02`, etc. Use adhocs only for low-risk work completed
|
||||||
|
directly. Promote anything requiring analysis, design, approval, dependencies, or
|
||||||
|
multiple planned phases into a normal workplan.
|
||||||
|
|
||||||
|
Ecosystem todos from other agents arrive as `[repo:agentic-resources]` hub tasks —
|
||||||
|
visible at session start. Pick one up by creating the workplan file, then registering
|
||||||
|
the workstream.
|
||||||
|
|
||||||
|
Task blocks use this shape:
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-NNNN-T01
|
||||||
|
status: wait | todo | progress | done | cancel
|
||||||
|
priority: high | medium | low
|
||||||
|
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
|
||||||
|
```
|
||||||
|
|
||||||
|
Status progression is `todo` → `progress` → `done`; use `wait` for waiting or
|
||||||
|
blocked work and `cancel` for stopped work.
|
||||||
|
|
||||||
|
<!-- Ralph Loop rules and HEUREKA sequence: ~/.claude/CLAUDE.md — do not duplicate here -->
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# Custodian Brief — agentic-resources
|
# Custodian Brief — agentic-resources
|
||||||
|
|
||||||
**Domain:** helix_forge
|
**Domain:** helix_forge
|
||||||
**Last synced:** 2026-06-07 11:46 UTC
|
**Last synced:** 2026-06-21 14:09 UTC
|
||||||
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
|
||||||
|
|
||||||
## Active Workstreams
|
## Active Workstreams
|
||||||
|
|||||||
18
.repo-classification.yaml
Normal file
18
.repo-classification.yaml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
repo_classification:
|
||||||
|
standard: Repo Classification Standard
|
||||||
|
version: '1.0'
|
||||||
|
classified_at: '2026-06-22'
|
||||||
|
classified_by: agent
|
||||||
|
category: project
|
||||||
|
domain: infotech
|
||||||
|
secondary_domains: []
|
||||||
|
capability_tags:
|
||||||
|
- automation
|
||||||
|
- orchestration
|
||||||
|
business_stake:
|
||||||
|
- technology
|
||||||
|
- product
|
||||||
|
- operations
|
||||||
|
business_mechanics:
|
||||||
|
- coordination
|
||||||
|
- operation
|
||||||
98
AGENTS.md
98
AGENTS.md
@@ -4,50 +4,13 @@
|
|||||||
|
|
||||||
**Purpose:** Iterating towards optimal agentic performance.
|
**Purpose:** Iterating towards optimal agentic performance.
|
||||||
|
|
||||||
**Domain:** helix_forge
|
**Domain:** infotech
|
||||||
**Repo slug:** agentic-resources
|
**Repo slug:** agentic-resources
|
||||||
**Topic ID:** `f39fa2a3-c491-414c-a91b-b4c5fcc6139c`
|
**Topic ID:** `f39fa2a3-c491-414c-a91b-b4c5fcc6139c`
|
||||||
**Workplan prefix:** `AGENTIC-WP-`
|
**Workplan prefix:** `AGENTIC-WP-`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dev Workflow
|
|
||||||
|
|
||||||
The deliverable code lives in `session_memory/` (the Helix Forge coding-session
|
|
||||||
memory system). It is **pure-stdlib Python 3.11+** — `tomllib`, `sqlite3`,
|
|
||||||
`dataclasses`; no third-party runtime dependencies and no build step. `pytest` is
|
|
||||||
the only dev dependency. Run everything from the repo root.
|
|
||||||
|
|
||||||
| Need | Command |
|
|
||||||
|------|---------|
|
|
||||||
| Python | `python3` (3.11+ required for `tomllib`; developed on 3.12) |
|
|
||||||
| Install deps | none at runtime; for tests: `pip install pytest` (or `uv pip install pytest`) |
|
|
||||||
| Test | `python3 -m pytest` (full suite) · `python3 -m pytest tests/test_curate_review.py` (one file) · `-q` for quiet |
|
|
||||||
| Lint / build | none configured — keep changes matching surrounding style |
|
|
||||||
| Run: ingest sweep | `python3 -m session_memory.ingest` (`--dry-run`, `--config PATH`) |
|
|
||||||
| Run: detect | `python3 -m session_memory.detect` (`--json`, `--min-frequency N`) |
|
|
||||||
| Run: curate | `python3 -m session_memory.curate` (`--auto-approve`, `--json`) |
|
|
||||||
| Config | `session_memory/config.toml`; local store under `session_memory/.store/` (gitignored) |
|
|
||||||
|
|
||||||
**Verify a change before declaring it done:** run `python3 -m pytest` (expect all
|
|
||||||
green), and for pipeline changes do a live `ingest → detect → curate` pass against
|
|
||||||
the local store. See `session_memory/README.md` for the full layout and the
|
|
||||||
detect → curate → distribute flow.
|
|
||||||
|
|
||||||
### Editing files — Read before you Edit
|
|
||||||
|
|
||||||
**Read a file (or the region you'll touch) before Edit/Write.** The most common
|
|
||||||
error across our own captured coding sessions was *"File has not been read yet.
|
|
||||||
Read it first before writing to it"* — 12 of 27 real sessions, 8 repos
|
|
||||||
(`docs/ASSESSMENT-infra-friction.md`). Two cheap reflexes eliminate it:
|
|
||||||
|
|
||||||
- **Read → then Edit/Write.** Don't blind-write a file you haven't read this
|
|
||||||
session; the edit tools reject it and the retry wastes a turn.
|
|
||||||
- **On `File has been modified since read`, re-Read then re-Edit.** A stale read
|
|
||||||
means the file changed under you — refresh before retrying, don't loop.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## State Hub Integration
|
## State Hub Integration
|
||||||
|
|
||||||
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
|
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
|
||||||
@@ -138,6 +101,63 @@ curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## Credential and access routing
|
||||||
|
|
||||||
|
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
|
||||||
|
for inference. Run this check **before** requesting secrets, API keys, SSH access,
|
||||||
|
login tokens, or database passwords — in any repo, not only `ops-warden`.
|
||||||
|
|
||||||
|
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
|
||||||
|
other credential need belongs to another subsystem. **Do not** message
|
||||||
|
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
|
||||||
|
|
||||||
|
### Lookup (do this first)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
warden route find "<describe your need>" --json
|
||||||
|
warden route show <catalog-id> --json
|
||||||
|
```
|
||||||
|
|
||||||
|
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
|
||||||
|
|
||||||
|
| Agent runtime | How to orient |
|
||||||
|
| --- | --- |
|
||||||
|
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=agentic-resources` is for coordination, not secret vending |
|
||||||
|
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workstreams; **still** use `warden route` for credential ownership |
|
||||||
|
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
|
||||||
|
|
||||||
|
### Quick routing table
|
||||||
|
|
||||||
|
| I need… | Owner | ops-warden executes? |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
|
||||||
|
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
|
||||||
|
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
|
||||||
|
| Authorization decision | flex-auth | No — route only |
|
||||||
|
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
|
||||||
|
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
|
||||||
|
|
||||||
|
### Anti-patterns (do not do these)
|
||||||
|
|
||||||
|
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
|
||||||
|
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
|
||||||
|
- Pasting secrets into Git, State Hub, workplans, logs, or chat
|
||||||
|
|
||||||
|
### Other capabilities (reuse-surface)
|
||||||
|
|
||||||
|
Non-credential capabilities are usually discovered through **reuse-surface** federation
|
||||||
|
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
|
||||||
|
every repo's agent instructions because it is high-frequency, high-risk, and easy to
|
||||||
|
get wrong.
|
||||||
|
|
||||||
|
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
|
||||||
|
|
||||||
|
<!-- REPO-AGENTS-EXTENSIONS -->
|
||||||
|
<!-- Append repo-specific agent instructions below this marker.
|
||||||
|
The state-hub template sync preserves content after this line. -->
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## Workplan Convention (ADR-001)
|
## Workplan Convention (ADR-001)
|
||||||
|
|
||||||
Work items originate as files in this repo — not in the hub. The hub is a
|
Work items originate as files in this repo — not in the hub. The hub is a
|
||||||
@@ -161,7 +181,7 @@ anything needing analysis, design, approval, dependencies, or multiple phases.
|
|||||||
id: AGENTIC-WP-NNNN
|
id: AGENTIC-WP-NNNN
|
||||||
type: workplan
|
type: workplan
|
||||||
title: "..."
|
title: "..."
|
||||||
domain: helix_forge
|
domain: infotech
|
||||||
repo: agentic-resources
|
repo: agentic-resources
|
||||||
status: proposed | ready | active | blocked | backlog | finished | archived
|
status: proposed | ready | active | blocked | backlog | finished | archived
|
||||||
owner: codex
|
owner: codex
|
||||||
|
|||||||
12
CLAUDE.md
Normal file
12
CLAUDE.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# agentic-resources — Claude Code Instructions
|
||||||
|
|
||||||
|
@SCOPE.md
|
||||||
|
@.claude/rules/repo-identity.md
|
||||||
|
@.claude/rules/session-protocol.md
|
||||||
|
@.claude/rules/first-session.md
|
||||||
|
@.claude/rules/workplan-convention.md
|
||||||
|
@.claude/rules/stack-and-commands.md
|
||||||
|
@.claude/rules/architecture.md
|
||||||
|
@.claude/rules/repo-boundary.md
|
||||||
|
@.claude/rules/credential-routing.md
|
||||||
|
@.claude/rules/agents.md
|
||||||
@@ -370,8 +370,89 @@ hub indexes).
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Next step: [AGENTIC-WP-0002] implements Phase 0 — the schema, the Claude
|
## 11. Project metrics correlation (kaizen-agentic)
|
||||||
collector, the Tier1/Tier2 store, and the budget-based eviction sweep.*
|
|
||||||
|
Helix Forge owns **fleet-level** session capture and digests (this repo). The
|
||||||
|
**kaizen-agentic** framework owns **project-scoped** agent execution metrics
|
||||||
|
(ADR-004: `.kaizen/metrics/<agent>/executions.jsonl`). The two layers correlate
|
||||||
|
by optional `helix_session_uid` on project records — link-by-reference, no
|
||||||
|
duplicate ingestion in either repo.
|
||||||
|
|
||||||
|
| Layer | Owner | Storage |
|
||||||
|
|-------|-------|---------|
|
||||||
|
| Fleet | agentic-resources (Helix Forge) | digest store (`digests` table) |
|
||||||
|
| Project | kaizen-agentic | `.kaizen/metrics/<agent>/executions.jsonl` |
|
||||||
|
|
||||||
|
**Cross-repo contract:** [Helix Forge Correlation Contract](https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/docs/integrations/helix-forge-correlation.md)
|
||||||
|
(kaizen-agentic). Field mapping from `Session.session_uid` → `helix_session_uid`,
|
||||||
|
`digest.cost` → `tokens`, `tool_histogram` MCP share → `infra_overhead_share`.
|
||||||
|
|
||||||
|
**Read path:** `kaizen-agentic metrics correlate <uid>` looks up a digest via
|
||||||
|
`HELIX_STORE_DB` (this repo's session store). No write path from kaizen-agentic
|
||||||
|
into Helix Forge.
|
||||||
|
|
||||||
|
**Related kaizen-agentic docs:** [ADR-004 project metrics convention](https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/docs/adr/ADR-004-project-metrics-convention.md),
|
||||||
|
[wiki/EcosystemIntegration.md](https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/wiki/EcosystemIntegration.md).
|
||||||
|
|
||||||
|
### 11.1 Session-close env export (dual-layer agents)
|
||||||
|
|
||||||
|
Agents that run **both** Helix Forge capture and kaizen `metrics record` should
|
||||||
|
export the following **after** the ingest sweep has written the session digest
|
||||||
|
(`python -m session_memory.ingest` or an equivalent Stop/SessionEnd hook). Names
|
||||||
|
match kaizen-agentic ADR-004 — do not invent parallel aliases.
|
||||||
|
|
||||||
|
| Variable | Source in Helix Forge | Purpose |
|
||||||
|
|----------|----------------------|---------|
|
||||||
|
| `HELIX_SESSION_UID` | `Session.session_uid` | Primary correlation key → `helix_session_uid` |
|
||||||
|
| `HELIX_REPO` | `digest.repo` | Project/repo scoping |
|
||||||
|
| `HELIX_FLAVOR` | `digest.flavor` | Agent runtime (`claude` / `codex` / `grok`) |
|
||||||
|
| `HELIX_TOKENS` | `digest.cost.input_tokens + digest.cost.output_tokens` | Token rollup → `tokens` |
|
||||||
|
| `HELIX_INFRA_OVERHEAD_SHARE` | infra bucket share over `tool_histogram` (see `measure.metrics.session_metrics`) | MCP/plumbing overhead → `infra_overhead_share` |
|
||||||
|
|
||||||
|
Example (after digest exists):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export HELIX_SESSION_UID="claude:abc-123"
|
||||||
|
export HELIX_REPO="agentic-resources"
|
||||||
|
export HELIX_FLAVOR="claude"
|
||||||
|
export HELIX_TOKENS=125000
|
||||||
|
export HELIX_INFRA_OVERHEAD_SHARE=0.117
|
||||||
|
# optional — lets kaizen correlate without guessing the store location:
|
||||||
|
export HELIX_STORE_DB="$(pwd)/session_memory/.store/mem.db"
|
||||||
|
kaizen-agentic metrics record # merges HELIX_* when present
|
||||||
|
```
|
||||||
|
|
||||||
|
### 11.2 Digest store location and read API
|
||||||
|
|
||||||
|
- **`HELIX_STORE_DB`** — absolute path to the SQLite file holding Tier 2 digests.
|
||||||
|
Defaults to `config.toml` `[store].db_path` (`session_memory/.store/mem.db` relative
|
||||||
|
to the repo root). Export as an absolute path when setting the variable on session
|
||||||
|
close so `metrics correlate` works across hosts and working directories.
|
||||||
|
- **Thin CLI** — `python -m session_memory.digest_lookup <session_uid> [--json]`
|
||||||
|
prints one digest without running ingest. Exit `0` on hit, `1` when missing.
|
||||||
|
- **Programmatic** — `Store.get_digest(session_uid)` returns the JSON blob written
|
||||||
|
by `build_digest` / `analyze`.
|
||||||
|
|
||||||
|
**Stable digest JSON shape** (fields consumers may rely on):
|
||||||
|
|
||||||
|
| Field | Type | Notes |
|
||||||
|
|-------|------|-------|
|
||||||
|
| `session_uid` | string | Normalized uid (`<flavor>:<native-id>`) |
|
||||||
|
| `flavor`, `repo`, `domain` | string | Session attribution |
|
||||||
|
| `model` | string | Model id when known |
|
||||||
|
| `started_at`, `ended_at` | string | ISO timestamps |
|
||||||
|
| `outcome` | string | `success` / `fail` / `abandoned` / `unknown` |
|
||||||
|
| `cost` | object | `input_tokens`, `output_tokens`, `cache_tokens`, `wall_clock_s`, `turns`, `retries` |
|
||||||
|
| `tool_histogram` | object | Tool name → call count |
|
||||||
|
| `event_count`, `kind_counts`, `markers` | object/int | Compact activity summary |
|
||||||
|
| `first_prompt`, `last_assistant` | string | Short text snippets |
|
||||||
|
| `error_snippets` | array | `{fingerprint, sample, count, tool}` entries |
|
||||||
|
| `schema_version` | int | Digest schema version |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Implemented:* Phases 0–4, weekly retro ([AGENTIC-WP-0002]–[AGENTIC-WP-0010]);
|
||||||
|
kaizen correlation follow-up ([AGENTIC-WP-0011]).
|
||||||
|
|
||||||
## Sources
|
## Sources
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
**Status:** Draft v0.1
|
**Status:** Draft v0.1
|
||||||
**Author:** Claude (drafted with Bernd Worsch)
|
**Author:** Claude (drafted with Bernd Worsch)
|
||||||
**Created:** 2026-06-06
|
**Created:** 2026-06-06
|
||||||
**Updated:** 2026-06-06
|
**Updated:** 2026-06-19
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -223,6 +223,32 @@ record:
|
|||||||
- The hub remains a **read model**; Helix Forge writes its durable artifacts as files
|
- The hub remains a **read model**; Helix Forge writes its durable artifacts as files
|
||||||
and lets the hub index them.
|
and lets the hub index them.
|
||||||
|
|
||||||
|
### 9.1 Downstream: kaizen-agentic project metrics correlation
|
||||||
|
|
||||||
|
Helix Forge is a **fleet-level** producer of normalized session digests. The
|
||||||
|
**kaizen-agentic** framework is a **project-scoped** consumer of optional
|
||||||
|
correlation fields on its execution metrics (ADR-004). The two layers link
|
||||||
|
**by reference** — kaizen-agentic does not re-implement JSONL ingestion or write
|
||||||
|
into the Helix Forge store.
|
||||||
|
|
||||||
|
| Layer | Owner | What it stores |
|
||||||
|
|-------|-------|----------------|
|
||||||
|
| Fleet | agentic-resources (`session_memory`) | Per-session digests in the local SQLite store |
|
||||||
|
| Project | kaizen-agentic | `.kaizen/metrics/<agent>/executions.jsonl` |
|
||||||
|
|
||||||
|
**Canonical spec in this repo:** [DESIGN-session-memory.md §11](DESIGN-session-memory.md#11-project-metrics-correlation-kaizen-agentic)
|
||||||
|
(session-close env export, digest read path, stable JSON shape).
|
||||||
|
|
||||||
|
**Authoritative cross-repo contract (kaizen-agentic):**
|
||||||
|
[Helix Forge Correlation Contract](https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/docs/integrations/helix-forge-correlation.md).
|
||||||
|
Field mapping: `Session.session_uid` → `helix_session_uid`; digest token totals →
|
||||||
|
`tokens`; MCP/tool overhead share → `infra_overhead_share`.
|
||||||
|
|
||||||
|
**Read path for consumers:** `HELIX_STORE_DB` points at the digest SQLite file
|
||||||
|
(default `session_memory/.store/mem.db`); `python -m session_memory.digest_lookup
|
||||||
|
<uid> --json` or `kaizen-agentic metrics correlate <uid>` performs a read-only
|
||||||
|
lookup. No ingestion code belongs in kaizen-agentic.
|
||||||
|
|
||||||
## 10. Success Metrics
|
## 10. Success Metrics
|
||||||
|
|
||||||
| Metric | Meaning | Target (directional, v1) |
|
| Metric | Meaning | Target (directional, v1) |
|
||||||
|
|||||||
12
registry/README.md
Normal file
12
registry/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Capability Registry
|
||||||
|
|
||||||
|
Markdown-first capability index for federation and reuse planning.
|
||||||
|
|
||||||
|
## Authoring
|
||||||
|
|
||||||
|
1. Copy a capability entry template (see reuse-surface `templates/capability-entry.template.md`).
|
||||||
|
2. Add the row to `indexes/capabilities.yaml`.
|
||||||
|
3. Run `reuse-surface validate` from a checkout with the CLI installed.
|
||||||
|
4. Merge to `main` and verify publish with `reuse-surface establish --publish-check`.
|
||||||
|
|
||||||
|
Federation contract: reuse-surface `docs/RegistryFederation.md`.
|
||||||
0
registry/capabilities/.gitkeep
Normal file
0
registry/capabilities/.gitkeep
Normal file
4
registry/indexes/capabilities.yaml
Normal file
4
registry/indexes/capabilities.yaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
version: 1
|
||||||
|
updated: '2026-06-16'
|
||||||
|
domain: helix_forge
|
||||||
|
capabilities: []
|
||||||
@@ -42,6 +42,10 @@ session_memory/
|
|||||||
measure/metrics.py # fleet metrics + persisted baseline snapshots
|
measure/metrics.py # fleet metrics + persisted baseline snapshots
|
||||||
measure/effect.py # before/after per-pattern effectiveness
|
measure/effect.py # before/after per-pattern effectiveness
|
||||||
measure/__main__.py # python -m session_memory.measure
|
measure/__main__.py # python -m session_memory.measure
|
||||||
|
retro/build.py # windowed top-3-per-repo suggestions
|
||||||
|
retro/publish.py # hub coding_retro read model + local report
|
||||||
|
retro/__main__.py # python -m session_memory.retro
|
||||||
|
digest_lookup.py # python -m session_memory.digest_lookup (read one digest, no ingest)
|
||||||
config.toml # store paths, retention caps, sources, repo->domain map, curate gate
|
config.toml # store paths, retention caps, sources, repo->domain map, curate gate
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -163,6 +167,57 @@ python -m session_memory.measure --no-save --json
|
|||||||
retired. Recorded pre-fix baseline (2026-06-07): 27 sessions, infra-overhead
|
retired. Recorded pre-fix baseline (2026-06-07): 27 sessions, infra-overhead
|
||||||
median 11.7 %, error rate 0.96, schema-thrash 8 sessions.
|
median 11.7 %, error rate 0.96, schema-thrash 8 sessions.
|
||||||
|
|
||||||
|
## Weekly retro (the input to the scheduled retrospection)
|
||||||
|
|
||||||
|
A windowed roll-up: detect + measure over the last N days → the **top-3
|
||||||
|
improvement suggestions per repo** (cross-flavor first; recommendations pulled
|
||||||
|
from the Pattern Catalog) → published to the hub as the `coding_retro` read model.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m session_memory.retro # last 7 days, local report
|
||||||
|
python -m session_memory.retro --window-days 30 --json
|
||||||
|
python -m session_memory.retro --publish # also post coding_retro to the hub
|
||||||
|
```
|
||||||
|
|
||||||
|
Writes `retro/last_retro.{json,md}` and (with `--publish`) posts an
|
||||||
|
`event_type=coding_retro` progress event. This is consumed by activity-core's
|
||||||
|
**Weekly Coding Retrospection** schedule (ACTIVITY-WP-0008, Saturday 19:00 Berlin),
|
||||||
|
which emits one improvement task per relevant repo. Hub publish degrades
|
||||||
|
gracefully when the hub is unreachable.
|
||||||
|
|
||||||
|
## Correlation with kaizen-agentic
|
||||||
|
|
||||||
|
Helix Forge owns **fleet-level** session digests; **kaizen-agentic** owns
|
||||||
|
**project-scoped** execution metrics (ADR-004). The two layers correlate by
|
||||||
|
optional `helix_session_uid` on project records — **link-by-reference only**;
|
||||||
|
kaizen-agentic does not ingest JSONL into this store.
|
||||||
|
|
||||||
|
| Layer | Storage |
|
||||||
|
|-------|---------|
|
||||||
|
| Fleet (here) | `session_memory/.store/mem.db` → `digests` table |
|
||||||
|
| Project (kaizen) | `.kaizen/metrics/<agent>/executions.jsonl` |
|
||||||
|
|
||||||
|
- **Spec:** [DESIGN-session-memory.md §11](../docs/DESIGN-session-memory.md#11-project-metrics-correlation-kaizen-agentic)
|
||||||
|
- **Contract (kaizen-agentic):** [Helix Forge Correlation Contract](https://gitea.coulomb.social/coulomb/kaizen-agentic/src/branch/main/docs/integrations/helix-forge-correlation.md)
|
||||||
|
|
||||||
|
### Session-close env export
|
||||||
|
|
||||||
|
After ingest has written the digest, agents using both layers export `HELIX_*`
|
||||||
|
vars for `kaizen-agentic metrics record` to merge (names match ADR-004):
|
||||||
|
|
||||||
|
`HELIX_SESSION_UID`, `HELIX_REPO`, `HELIX_FLAVOR`, `HELIX_TOKENS`,
|
||||||
|
`HELIX_INFRA_OVERHEAD_SHARE`, and optionally `HELIX_STORE_DB` (absolute path to
|
||||||
|
`mem.db`). See DESIGN §11.1 for field sources.
|
||||||
|
|
||||||
|
### Read one digest (for `metrics correlate`)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python -m session_memory.digest_lookup claude:abc-123 --json
|
||||||
|
HELIX_STORE_DB=/abs/path/to/mem.db python -m session_memory.digest_lookup <uid>
|
||||||
|
```
|
||||||
|
|
||||||
|
Defaults to `[store].db_path` in `config.toml`. Read-only — does not run ingest.
|
||||||
|
|
||||||
## Retention knobs (`[retention]` in config.toml)
|
## Retention knobs (`[retention]` in config.toml)
|
||||||
|
|
||||||
| Key | Meaning |
|
| Key | Meaning |
|
||||||
@@ -199,3 +254,7 @@ python -m pytest # schema, adapters, store, digest, retention, ingest,
|
|||||||
- **Phase 4** (AGENTIC-WP-0009): Measure — fleet baseline/trend + before/after
|
- **Phase 4** (AGENTIC-WP-0009): Measure — fleet baseline/trend + before/after
|
||||||
per-pattern effectiveness. The Capture → Detect → Curate → Distribute → Measure
|
per-pattern effectiveness. The Capture → Detect → Curate → Distribute → Measure
|
||||||
loop is closed.
|
loop is closed.
|
||||||
|
- **Weekly retro** (AGENTIC-WP-0010): windowed top-3-per-repo + hub `coding_retro`
|
||||||
|
publish.
|
||||||
|
- **Kaizen correlation** (AGENTIC-WP-0011): bidirectional doc links, session-close
|
||||||
|
`HELIX_*` env convention, `digest_lookup` read path.
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"created_at": "2026-06-07T09:13:20Z", "distribution_ready": true, "id": "sp-problem-budget_overrun-tokens", "name": "problem: budget overrun", "polarity": "problem", "problem": "problem: budget overrun", "provenance": {"detected_at": null, "evidence": {"cost_impact": 10.667, "cross_flavor": false, "flavors": ["claude"], "frequency": 3, "key": "problem:budget_overrun:tokens", "locus": "tokens", "polarity": "problem", "repos": ["artifact-store", "citation-evidence", "infospace-bench"], "score": 32.001, "sessions": ["claude:0ef1b45c-5c27-4e20-88b3-37daeaa24eca", "claude:6e0d3d68-872b-4d93-bb09-0691e091314b", "claude:8fabd5ce-6a20-4412-9a8b-0f0763394a78"], "signal_type": "budget_overrun", "title": "problem: budget overrun"}, "promoted_at": "2026-06-07T09:13:20Z", "source_key": "problem:budget_overrun:tokens"}, "rendering_hints": {"claude": {"note": "TODO: refine rendering", "target": "CLAUDE.md"}}, "resolutions": [{"detail": "", "steps": [], "summary": "TODO: capture the recommended resolution"}], "schema_version": 1, "scope": {"domains": [], "flavors": ["claude"], "repos": ["artifact-store", "citation-evidence", "infospace-bench"]}, "status": "superseded", "updated_at": "2026-06-07T09:13:20Z", "version": "1.0.0"}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
"created_at": "2026-06-07T09:13:20Z",
|
"created_at": "2026-06-07T09:13:20Z",
|
||||||
"distribution_ready": true,
|
"distribution_ready": true,
|
||||||
"id": "sp-problem-budget_overrun-tokens",
|
"id": "sp-problem-budget_overrun-tokens",
|
||||||
"name": "problem: budget overrun",
|
"name": "Budget overrun: token cost above peers",
|
||||||
"polarity": "problem",
|
"polarity": "problem",
|
||||||
"problem": "problem: budget overrun",
|
"problem": "A session's token cost lands well above its peers (>p90). Usually driven by re-reading large files or tool outputs, carrying redundant context, or long exploratory loops without checkpoints.",
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"detected_at": null,
|
"detected_at": null,
|
||||||
"evidence": {
|
"evidence": {
|
||||||
@@ -36,15 +36,27 @@
|
|||||||
},
|
},
|
||||||
"rendering_hints": {
|
"rendering_hints": {
|
||||||
"claude": {
|
"claude": {
|
||||||
"note": "TODO: refine rendering",
|
|
||||||
"target": "CLAUDE.md"
|
"target": "CLAUDE.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": [
|
"resolutions": [
|
||||||
{
|
{
|
||||||
"detail": "",
|
"detail": "Use offset/limit; don't re-Read a file already in the transcript.",
|
||||||
|
"steps": [
|
||||||
|
"Locate with grep/glob first",
|
||||||
|
"Read only the relevant span"
|
||||||
|
],
|
||||||
|
"summary": "Read narrowly \u2014 target the region you need, not whole large files"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "Summarize progress; avoid re-pulling outputs already shown.",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"summary": "TODO: capture the recommended resolution"
|
"summary": "Checkpoint and prune context instead of re-fetching it"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "grep/glob narrows scope far cheaper than reading whole trees.",
|
||||||
|
"steps": [],
|
||||||
|
"summary": "Prefer targeted search over broad reads to locate code"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
@@ -60,6 +72,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status": "approved",
|
"status": "approved",
|
||||||
"updated_at": "2026-06-07T09:13:20Z",
|
"updated_at": "2026-06-07T14:21:06Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"covers": [], "created_at": "2026-06-07T13:26:25Z", "distribution_ready": true, "id": "sp-problem-file_not_read-edit", "name": "Read before you Edit", "polarity": "problem", "problem": "Agents call Edit/Write on a file they have not read in the current session, or after it changed under them. The edit tools reject this ('File has not been read yet' / 'File has been modified since read'), and the retry burns a turn. Top recurring error in the corpus (12/27 sessions, 8 repos).", "provenance": {"detected_at": null, "evidence": {"frequency": 32, "origin": "AGENTIC-WP-0006 error mining / ASSESSMENT-infra-friction.md", "polarity": "problem", "repos": 8, "sessions": 12}, "promoted_at": null, "source_key": "problem:file_not_read:edit"}, "rendering_hints": {"claude": {"target": "CLAUDE.md"}, "codex": {"target": "AGENTS.md"}, "grok": {"target": ".grok/instructions.md"}}, "resolutions": [{"detail": "Never blind-write a file you haven't read this session.", "steps": ["Read the target file", "Then Edit/Write"], "summary": "Read the file (or the region you'll touch) before Edit/Write"}, {"detail": "A stale read means the file changed under you; refresh, don't loop.", "steps": ["Re-Read the file", "Re-apply the Edit"], "summary": "On 'modified since read', re-Read then re-Edit"}], "schema_version": 1, "scope": {"domains": [], "flavors": [], "repos": []}, "status": "superseded", "updated_at": "2026-06-07T13:26:25Z", "version": "1.0.0"}
|
||||||
@@ -1,4 +1,9 @@
|
|||||||
{
|
{
|
||||||
|
"covers": [
|
||||||
|
"file has not been read",
|
||||||
|
"modified since read",
|
||||||
|
"file_not_read"
|
||||||
|
],
|
||||||
"created_at": "2026-06-07T13:26:25Z",
|
"created_at": "2026-06-07T13:26:25Z",
|
||||||
"distribution_ready": true,
|
"distribution_ready": true,
|
||||||
"id": "sp-problem-file_not_read-edit",
|
"id": "sp-problem-file_not_read-edit",
|
||||||
@@ -53,6 +58,6 @@
|
|||||||
"repos": []
|
"repos": []
|
||||||
},
|
},
|
||||||
"status": "approved",
|
"status": "approved",
|
||||||
"updated_at": "2026-06-07T13:26:25Z",
|
"updated_at": "2026-06-07T19:06:45Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"created_at": "2026-06-07T09:13:20Z", "distribution_ready": false, "id": "sp-problem-infra_overhead-infra_overhead", "name": "problem: infra overhead", "polarity": "problem", "problem": "problem: infra overhead", "provenance": {"detected_at": null, "evidence": {"cost_impact": 0.801, "cross_flavor": false, "flavors": ["claude"], "frequency": 2, "key": "problem:infra_overhead:infra_overhead", "locus": "infra_overhead", "polarity": "problem", "repos": ["markitect-main", "vergabe-teilnahme"], "score": 1.602, "sessions": ["claude:135002f9-98d2-4d1b-b8fb-543b20388782", "claude:b4ae9631-a7eb-42a6-acb1-c65b660c4b74"], "signal_type": "infra_overhead", "title": "problem: infra overhead"}, "promoted_at": "2026-06-07T09:13:20Z", "source_key": "problem:infra_overhead:infra_overhead"}, "rendering_hints": {"claude": {"note": "TODO: refine rendering", "target": "CLAUDE.md"}}, "resolutions": [{"detail": "", "steps": [], "summary": "TODO: capture the recommended resolution"}], "schema_version": 1, "scope": {"domains": [], "flavors": ["claude"], "repos": ["markitect-main", "vergabe-teilnahme"]}, "status": "superseded", "updated_at": "2026-06-07T09:13:20Z", "version": "1.0.0"}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
"created_at": "2026-06-07T09:13:20Z",
|
"created_at": "2026-06-07T09:13:20Z",
|
||||||
"distribution_ready": false,
|
"distribution_ready": false,
|
||||||
"id": "sp-problem-infra_overhead-infra_overhead",
|
"id": "sp-problem-infra_overhead-infra_overhead",
|
||||||
"name": "problem: infra overhead",
|
"name": "Infrastructure overhead: too much coordination plumbing",
|
||||||
"polarity": "problem",
|
"polarity": "problem",
|
||||||
"problem": "problem: infra overhead",
|
"problem": "A large share of the session's tool calls are State Hub / task-management / schema-loading plumbing rather than touching the repo (corpus median 11.7%, up to 43% in the worst sessions; one session made 231 hub calls).",
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"detected_at": null,
|
"detected_at": null,
|
||||||
"evidence": {
|
"evidence": {
|
||||||
@@ -34,15 +34,27 @@
|
|||||||
},
|
},
|
||||||
"rendering_hints": {
|
"rendering_hints": {
|
||||||
"claude": {
|
"claude": {
|
||||||
"note": "TODO: refine rendering",
|
|
||||||
"target": "CLAUDE.md"
|
"target": "CLAUDE.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": [
|
"resolutions": [
|
||||||
{
|
{
|
||||||
"detail": "",
|
"detail": "Update several task statuses together; emit fewer, coarser progress events.",
|
||||||
|
"steps": [
|
||||||
|
"Do a chunk of work",
|
||||||
|
"Then sync statuses in one pass"
|
||||||
|
],
|
||||||
|
"summary": "Batch hub writes \u2014 sync at checkpoints, not per event"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "One scoped summary at session start beats many broad reads.",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"summary": "TODO: capture the recommended resolution"
|
"summary": "Orient once with get_domain_summary, don't re-query repeatedly"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "See STATE-WP-0058 \u2014 stops the repeated ToolSearch for hub tools.",
|
||||||
|
"steps": [],
|
||||||
|
"summary": "Front-load hub tool knowledge via the State Hub skill"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
@@ -57,6 +69,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status": "provisional",
|
"status": "provisional",
|
||||||
"updated_at": "2026-06-07T09:13:20Z",
|
"updated_at": "2026-06-07T14:21:06Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"created_at": "2026-06-07T09:13:20Z", "distribution_ready": true, "id": "sp-problem-schema_thrash-schema_load", "name": "problem: schema thrash", "polarity": "problem", "problem": "problem: schema thrash", "provenance": {"detected_at": null, "evidence": {"cost_impact": 79.0, "cross_flavor": false, "flavors": ["claude"], "frequency": 8, "key": "problem:schema_thrash:schema_load", "locus": "schema_load", "polarity": "problem", "repos": ["activity-core", "citation-evidence", "flex-auth", "infospace-bench", "ops-bridge", "vergabe-teilnahme"], "score": 632.0, "sessions": ["claude:0ef1b45c-5c27-4e20-88b3-37daeaa24eca", "claude:30dbad62-c042-41f2-80c1-5953a1100e7f", "claude:4340b160-2fb6-47d0-897c-3cac0a8855d8", "claude:63fd4df2-5add-4748-af21-c1544825e006", "claude:8313f946-f008-4e98-9915-31950380e39e", "claude:8fabd5ce-6a20-4412-9a8b-0f0763394a78", "claude:b4ae9631-a7eb-42a6-acb1-c65b660c4b74", "claude:bbcf1c2b-14be-40e4-826b-4b2b49b9d212"], "signal_type": "schema_thrash", "title": "problem: schema thrash"}, "promoted_at": "2026-06-07T09:13:20Z", "source_key": "problem:schema_thrash:schema_load"}, "rendering_hints": {"claude": {"note": "TODO: refine rendering", "target": "CLAUDE.md"}}, "resolutions": [{"detail": "", "steps": [], "summary": "TODO: capture the recommended resolution"}], "schema_version": 1, "scope": {"domains": [], "flavors": ["claude"], "repos": ["activity-core", "citation-evidence", "flex-auth", "infospace-bench", "ops-bridge", "vergabe-teilnahme"]}, "status": "superseded", "updated_at": "2026-06-07T09:13:20Z", "version": "1.0.0"}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
"created_at": "2026-06-07T09:13:20Z",
|
"created_at": "2026-06-07T09:13:20Z",
|
||||||
"distribution_ready": true,
|
"distribution_ready": true,
|
||||||
"id": "sp-problem-schema_thrash-schema_load",
|
"id": "sp-problem-schema_thrash-schema_load",
|
||||||
"name": "problem: schema thrash",
|
"name": "Schema thrash: repeated ToolSearch",
|
||||||
"polarity": "problem",
|
"polarity": "problem",
|
||||||
"problem": "problem: schema thrash",
|
"problem": "ToolSearch fires repeatedly within a session (seen in 81% of sessions) because the State Hub MCP tools are deferred and their schemas get re-loaded each time they are needed \u2014 pure overhead with no work value.",
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"detected_at": null,
|
"detected_at": null,
|
||||||
"evidence": {
|
"evidence": {
|
||||||
@@ -44,15 +44,22 @@
|
|||||||
},
|
},
|
||||||
"rendering_hints": {
|
"rendering_hints": {
|
||||||
"claude": {
|
"claude": {
|
||||||
"note": "TODO: refine rendering",
|
|
||||||
"target": "CLAUDE.md"
|
"target": "CLAUDE.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": [
|
"resolutions": [
|
||||||
{
|
{
|
||||||
"detail": "",
|
"detail": "Resolve them by name in one ToolSearch (select:...) rather than searching ad hoc.",
|
||||||
|
"steps": [
|
||||||
|
"List the hub tools the session needs",
|
||||||
|
"Load them once at the start"
|
||||||
|
],
|
||||||
|
"summary": "Load the tool schemas you'll need once, up front"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "The skill carries the schemas so no per-use discovery is needed.",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"summary": "TODO: capture the recommended resolution"
|
"summary": "Adopt the State Hub skill that front-loads common hub tool signatures"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
@@ -71,6 +78,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status": "approved",
|
"status": "approved",
|
||||||
"updated_at": "2026-06-07T09:13:20Z",
|
"updated_at": "2026-06-07T14:21:06Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"created_at": "2026-06-07T09:13:20Z", "distribution_ready": true, "id": "sp-problem-tool_thrash-tool-bash", "name": "problem: tool thrash", "polarity": "problem", "problem": "problem: tool thrash", "provenance": {"detected_at": null, "evidence": {"cost_impact": 1990.0, "cross_flavor": false, "flavors": ["claude"], "frequency": 11, "key": "problem:tool_thrash:tool:Bash", "locus": "tool:Bash", "polarity": "problem", "repos": ["activity-core", "artifact-store", "citation-evidence", "ihp-railiance-probe", "infospace-bench", "railiance-apps", "state-hub", "vergabe-teilnahme"], "score": 21890.0, "sessions": ["claude:0ef1b45c-5c27-4e20-88b3-37daeaa24eca", "claude:2c0d14e1-d089-4076-bf35-b134737a261d", "claude:30dbad62-c042-41f2-80c1-5953a1100e7f", "claude:4307eff6-cd39-4189-be58-79a3acb69d6c", "claude:4340b160-2fb6-47d0-897c-3cac0a8855d8", "claude:6e0d3d68-872b-4d93-bb09-0691e091314b", "claude:8313f946-f008-4e98-9915-31950380e39e", "claude:8fabd5ce-6a20-4412-9a8b-0f0763394a78", "claude:a9483f07-c9dc-4f71-9fa0-831790ea965e", "claude:b1dfbcfa-91f9-4540-823a-26fcfaab7fc8", "claude:b4ae9631-a7eb-42a6-acb1-c65b660c4b74"], "signal_type": "tool_thrash", "title": "problem: tool thrash"}, "promoted_at": "2026-06-07T09:13:20Z", "source_key": "problem:tool_thrash:tool:Bash"}, "rendering_hints": {"claude": {"note": "TODO: refine rendering", "target": "CLAUDE.md"}}, "resolutions": [{"detail": "", "steps": [], "summary": "TODO: capture the recommended resolution"}], "schema_version": 1, "scope": {"domains": [], "flavors": ["claude"], "repos": ["activity-core", "artifact-store", "citation-evidence", "ihp-railiance-probe", "infospace-bench", "railiance-apps", "state-hub", "vergabe-teilnahme"]}, "status": "superseded", "updated_at": "2026-06-07T09:13:20Z", "version": "1.0.0"}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
"created_at": "2026-06-07T09:13:20Z",
|
"created_at": "2026-06-07T09:13:20Z",
|
||||||
"distribution_ready": true,
|
"distribution_ready": true,
|
||||||
"id": "sp-problem-tool_thrash-tool-bash",
|
"id": "sp-problem-tool_thrash-tool-bash",
|
||||||
"name": "problem: tool thrash",
|
"name": "Tool thrash: one tool hammered",
|
||||||
"polarity": "problem",
|
"polarity": "problem",
|
||||||
"problem": "problem: tool thrash",
|
"problem": "A single tool (often Bash or Edit) is invoked far more than any other in a session \u2014 a sign of trial-and-error churn or missing higher-level tooling.",
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"detected_at": null,
|
"detected_at": null,
|
||||||
"evidence": {
|
"evidence": {
|
||||||
@@ -49,15 +49,27 @@
|
|||||||
},
|
},
|
||||||
"rendering_hints": {
|
"rendering_hints": {
|
||||||
"claude": {
|
"claude": {
|
||||||
"note": "TODO: refine rendering",
|
|
||||||
"target": "CLAUDE.md"
|
"target": "CLAUDE.md"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": [
|
"resolutions": [
|
||||||
{
|
{
|
||||||
"detail": "",
|
"detail": "Compose a single command/script; run independent calls in parallel.",
|
||||||
|
"steps": [
|
||||||
|
"Group the steps",
|
||||||
|
"Run them as one block"
|
||||||
|
],
|
||||||
|
"summary": "Batch related shell work into one script, not many small Bash calls"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "Read the region, then one substantive Edit beats many tiny ones.",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"summary": "TODO: capture the recommended resolution"
|
"summary": "Make fewer, larger edits with full context"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "If the same invocation recurs, wrap it once.",
|
||||||
|
"steps": [],
|
||||||
|
"summary": "Factor a repeated command pattern into a helper"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
@@ -78,6 +90,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status": "approved",
|
"status": "approved",
|
||||||
"updated_at": "2026-06-07T09:13:20Z",
|
"updated_at": "2026-06-07T14:21:06Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
{"created_at": "2026-06-07T09:13:20Z", "distribution_ready": true, "id": "sp-success-clean_pass-outcome", "name": "cross-flavor success: clean pass", "polarity": "success", "problem": "cross-flavor success: clean pass", "provenance": {"detected_at": null, "evidence": {"cost_impact": 17.0, "cross_flavor": true, "flavors": ["claude", "grok"], "frequency": 17, "key": "success:clean_pass:outcome", "locus": "outcome", "polarity": "success", "repos": ["activity-core", "agentic-resources", "artifact-store", "can-you-assist", "citation-evidence", "infospace-bench", "issue-facade", "ops-bridge", "railiance-apps", "state-hub", "the-custodian", "vergabe-teilnahme"], "score": 433.5, "sessions": ["claude:0ef1b45c-5c27-4e20-88b3-37daeaa24eca", "claude:16bdbec4-b018-4902-9fb5-336f8f3d61c8", "claude:2c0d14e1-d089-4076-bf35-b134737a261d", "claude:30dbad62-c042-41f2-80c1-5953a1100e7f", "claude:4307eff6-cd39-4189-be58-79a3acb69d6c", "claude:4340b160-2fb6-47d0-897c-3cac0a8855d8", "claude:631de76e-fdee-43b5-b091-7b7675467ad1", "claude:63fd4df2-5add-4748-af21-c1544825e006", "claude:6e0d3d68-872b-4d93-bb09-0691e091314b", "claude:8313f946-f008-4e98-9915-31950380e39e", "claude:8fabd5ce-6a20-4412-9a8b-0f0763394a78", "claude:a9483f07-c9dc-4f71-9fa0-831790ea965e", "claude:b4ae9631-a7eb-42a6-acb1-c65b660c4b74", "claude:eb837dd1-5b8e-472e-b9e1-4537b10e03e6", "claude:ee9e84f2-bc35-4eb5-a7ad-aaec5f31d965", "claude:f1b25697-0e5f-45f0-81d1-af0f1762c438", "grok:019e6122-00c0-79f3-b4e5-9c70b77c015d"], "signal_type": "clean_pass", "title": "cross-flavor success: clean pass"}, "promoted_at": "2026-06-07T09:13:20Z", "source_key": "success:clean_pass:outcome"}, "rendering_hints": {"claude": {"note": "TODO: refine rendering", "target": "CLAUDE.md"}, "grok": {"note": "TODO: refine rendering", "target": "instructions"}}, "resolutions": [{"detail": "", "steps": [], "summary": "TODO: capture the recommended resolution"}], "schema_version": 1, "scope": {"domains": [], "flavors": ["claude", "grok"], "repos": ["activity-core", "agentic-resources", "artifact-store", "can-you-assist", "citation-evidence", "infospace-bench", "issue-facade", "ops-bridge", "railiance-apps", "state-hub", "the-custodian", "vergabe-teilnahme"]}, "status": "superseded", "updated_at": "2026-06-07T09:13:20Z", "version": "1.0.0"}
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
"created_at": "2026-06-07T09:13:20Z",
|
"created_at": "2026-06-07T09:13:20Z",
|
||||||
"distribution_ready": true,
|
"distribution_ready": true,
|
||||||
"id": "sp-success-clean_pass-outcome",
|
"id": "sp-success-clean_pass-outcome",
|
||||||
"name": "cross-flavor success: clean pass",
|
"name": "Clean pass: tests green, no retries",
|
||||||
"polarity": "success",
|
"polarity": "success",
|
||||||
"problem": "cross-flavor success: clean pass",
|
"problem": "The target session shape: ends in success, runs the test suite, with no errors and no retries \u2014 resolves cheaply and reliably. Seen across many sessions and both Claude and Grok (the highest-value pattern to reinforce).",
|
||||||
"provenance": {
|
"provenance": {
|
||||||
"detected_at": null,
|
"detected_at": null,
|
||||||
"evidence": {
|
"evidence": {
|
||||||
@@ -60,19 +60,26 @@
|
|||||||
},
|
},
|
||||||
"rendering_hints": {
|
"rendering_hints": {
|
||||||
"claude": {
|
"claude": {
|
||||||
"note": "TODO: refine rendering",
|
|
||||||
"target": "CLAUDE.md"
|
"target": "CLAUDE.md"
|
||||||
},
|
},
|
||||||
"grok": {
|
"grok": {
|
||||||
"note": "TODO: refine rendering",
|
|
||||||
"target": "instructions"
|
"target": "instructions"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"resolutions": [
|
"resolutions": [
|
||||||
{
|
{
|
||||||
"detail": "",
|
"detail": "A passing suite is the cheapest proof the change works.",
|
||||||
|
"steps": [
|
||||||
|
"Make the change",
|
||||||
|
"Run the suite",
|
||||||
|
"Only then report done"
|
||||||
|
],
|
||||||
|
"summary": "Run the test suite before declaring done; let green gate completion"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"detail": "Small verified steps beat large unverified ones that bounce.",
|
||||||
"steps": [],
|
"steps": [],
|
||||||
"summary": "TODO: capture the recommended resolution"
|
"summary": "Work incrementally and verify as you go to avoid retries"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"schema_version": 1,
|
"schema_version": 1,
|
||||||
@@ -98,6 +105,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"status": "approved",
|
"status": "approved",
|
||||||
"updated_at": "2026-06-07T09:13:20Z",
|
"updated_at": "2026-06-07T14:21:06Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ min_prompt_len = 25 # first prompt shorter than this is treated as trivial
|
|||||||
[measure]
|
[measure]
|
||||||
baselines = "session_memory/measure/baselines.jsonl" # timestamped metric snapshots (committed)
|
baselines = "session_memory/measure/baselines.jsonl" # timestamped metric snapshots (committed)
|
||||||
|
|
||||||
|
# Weekly retro (AGENTIC-WP-0010): windowed top-3-per-repo report, published to the
|
||||||
|
# hub as the coding_retro read model that activity-core's weekly schedule consumes.
|
||||||
|
[retro]
|
||||||
|
window_days = 7
|
||||||
|
report_json = "session_memory/retro/last_retro.json" # latest report (committed)
|
||||||
|
report_md = "session_memory/retro/last_retro.md" # human-readable mirror
|
||||||
|
hub_url = "http://127.0.0.1:8000" # for --publish (best-effort)
|
||||||
|
|
||||||
# Distribute phase (AGENTIC-WP-0007): where per-flavor proposals + the active
|
# Distribute phase (AGENTIC-WP-0007): where per-flavor proposals + the active
|
||||||
# registry are written. Proposals are HITL — reviewed, never auto-applied.
|
# registry are written. Proposals are HITL — reviewed, never auto-applied.
|
||||||
[distribute]
|
[distribute]
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ from .schema import SolutionPattern
|
|||||||
# Content fields that define a pattern's substance. Version, timestamps, status,
|
# Content fields that define a pattern's substance. Version, timestamps, status,
|
||||||
# and distribution_ready are metadata — changes to them never bump the version.
|
# and distribution_ready are metadata — changes to them never bump the version.
|
||||||
_CONTENT_KEYS = ("name", "polarity", "problem", "resolutions", "scope",
|
_CONTENT_KEYS = ("name", "polarity", "problem", "resolutions", "scope",
|
||||||
"provenance", "rendering_hints")
|
"provenance", "rendering_hints", "covers")
|
||||||
|
|
||||||
ADDED = "added"
|
ADDED = "added"
|
||||||
UNCHANGED = "unchanged"
|
UNCHANGED = "unchanged"
|
||||||
@@ -86,6 +86,22 @@ class Catalog:
|
|||||||
with open(path, encoding="utf-8") as fh:
|
with open(path, encoding="utf-8") as fh:
|
||||||
return [json.loads(line) for line in fh if line.strip()]
|
return [json.loads(line) for line in fh if line.strip()]
|
||||||
|
|
||||||
|
def find_for(self, signal_key: str, locus: str = "") -> Optional[SolutionPattern]:
|
||||||
|
"""Best catalog pattern for a detect signal: exact id first, then ``covers``.
|
||||||
|
|
||||||
|
Lets a signal that doesn't share a pattern's exact key (e.g. a
|
||||||
|
``recurring_error`` fingerprint) inherit the curated recommendation when a
|
||||||
|
pattern declares it covers that text.
|
||||||
|
"""
|
||||||
|
exact = self.load(SolutionPattern.make_id(signal_key))
|
||||||
|
if exact is not None:
|
||||||
|
return exact
|
||||||
|
hay = f"{signal_key} {locus}".lower()
|
||||||
|
for p in self.list(): # sorted by id -> deterministic
|
||||||
|
if any(c.lower() in hay for c in p.covers):
|
||||||
|
return p
|
||||||
|
return None
|
||||||
|
|
||||||
# --- the single write path ---------------------------------------------
|
# --- the single write path ---------------------------------------------
|
||||||
|
|
||||||
def upsert(self, pattern: SolutionPattern) -> str:
|
def upsert(self, pattern: SolutionPattern) -> str:
|
||||||
|
|||||||
@@ -81,6 +81,11 @@ class SolutionPattern:
|
|||||||
# per-flavor rendering hints, kept OUT of the agnostic core (OQ4):
|
# per-flavor rendering hints, kept OUT of the agnostic core (OQ4):
|
||||||
# {"claude": {...}, "codex": {...}, "grok": {...}}
|
# {"claude": {...}, "codex": {...}, "grok": {...}}
|
||||||
rendering_hints: dict[str, dict[str, Any]] = field(default_factory=dict)
|
rendering_hints: dict[str, dict[str, Any]] = field(default_factory=dict)
|
||||||
|
# other signal keys/loci this pattern's recommendation also applies to —
|
||||||
|
# lowercase substrings matched against a candidate signal's key+locus, so a
|
||||||
|
# detect signal that doesn't share this pattern's exact key (e.g. a
|
||||||
|
# recurring_error fingerprint) can still inherit the curated resolution.
|
||||||
|
covers: list[str] = field(default_factory=list)
|
||||||
status: str = "provisional"
|
status: str = "provisional"
|
||||||
distribution_ready: bool = False
|
distribution_ready: bool = False
|
||||||
created_at: Optional[str] = None
|
created_at: Optional[str] = None
|
||||||
|
|||||||
76
session_memory/digest_lookup.py
Normal file
76
session_memory/digest_lookup.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
"""Read a single session digest from the local store (AGENTIC-WP-0011 T03).
|
||||||
|
|
||||||
|
Thin read path for ``kaizen-agentic metrics correlate`` and other consumers.
|
||||||
|
Does not run ingest.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python -m session_memory.digest_lookup <session_uid> [--json]
|
||||||
|
HELIX_STORE_DB=/abs/path/to/mem.db python -m session_memory.digest_lookup <uid>
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from .core.store import Store
|
||||||
|
from .ingest import _expand, load_config
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_store_paths(*, config_path: str | None = None) -> tuple[str, str]:
|
||||||
|
"""Resolve db + blob paths from HELIX_STORE_DB or config.toml [store]."""
|
||||||
|
env_db = os.environ.get("HELIX_STORE_DB")
|
||||||
|
if env_db:
|
||||||
|
db_path = _expand(env_db)
|
||||||
|
blob_dir = os.path.join(os.path.dirname(db_path), "blobs")
|
||||||
|
return db_path, blob_dir
|
||||||
|
|
||||||
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
cfg_path = config_path or os.path.join(here, "config.toml")
|
||||||
|
store_cfg = load_config(cfg_path).get("store", {})
|
||||||
|
return _expand(store_cfg.get("db_path", "session_memory/.store/mem.db")), _expand(
|
||||||
|
store_cfg.get("blob_dir", "session_memory/.store/blobs")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def lookup_digest(session_uid: str, *, config_path: str | None = None) -> dict | None:
|
||||||
|
db_path, blob_dir = resolve_store_paths(config_path=config_path)
|
||||||
|
store = Store(db_path, blob_dir)
|
||||||
|
try:
|
||||||
|
return store.get_digest(session_uid)
|
||||||
|
finally:
|
||||||
|
store.close()
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
here = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ap = argparse.ArgumentParser(
|
||||||
|
description="Read one session digest from the Helix Forge store (no ingest)."
|
||||||
|
)
|
||||||
|
ap.add_argument("session_uid", help="Normalized session uid, e.g. claude:abc-123")
|
||||||
|
ap.add_argument("--config", default=os.path.join(here, "config.toml"),
|
||||||
|
help="config.toml when HELIX_STORE_DB is unset")
|
||||||
|
ap.add_argument("--json", action="store_true", help="print digest JSON to stdout")
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
digest = lookup_digest(args.session_uid, config_path=args.config)
|
||||||
|
if digest is None:
|
||||||
|
print(f"digest not found: {args.session_uid}", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(digest, indent=2, sort_keys=True))
|
||||||
|
else:
|
||||||
|
cost = digest.get("cost") or {}
|
||||||
|
tokens = cost.get("input_tokens", 0) + cost.get("output_tokens", 0)
|
||||||
|
print(f"session_uid: {digest.get('session_uid')}")
|
||||||
|
print(f"repo: {digest.get('repo')} flavor: {digest.get('flavor')}")
|
||||||
|
print(f"outcome: {digest.get('outcome')} tokens: {tokens}")
|
||||||
|
print(f"started_at: {digest.get('started_at')} ended_at: {digest.get('ended_at')}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -4,7 +4,143 @@
|
|||||||
"pattern_id": "sp-problem-file_not_read-edit",
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
"repo": "agentic-resources",
|
"repo": "agentic-resources",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:26:26Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "codex",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "agentic-resources",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "grok",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "agentic-resources",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "claude",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "can-you-assist",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "codex",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "can-you-assist",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "grok",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "can-you-assist",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "claude",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "net-kingdom",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "codex",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "net-kingdom",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "grok",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "net-kingdom",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "claude",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "ops-bridge",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "codex",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "ops-bridge",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "grok",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "ops-bridge",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "claude",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "state-hub",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "codex",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "state-hub",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "grok",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "state-hub",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "claude",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "the-custodian",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "codex",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "the-custodian",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
|
"version": "1.0.0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"flavor": "grok",
|
||||||
|
"pattern_id": "sp-problem-file_not_read-edit",
|
||||||
|
"repo": "the-custodian",
|
||||||
|
"status": "proposed",
|
||||||
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.0"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -12,95 +148,95 @@
|
|||||||
"pattern_id": "sp-problem-schema_thrash-schema_load",
|
"pattern_id": "sp-problem-schema_thrash-schema_load",
|
||||||
"repo": "ops-bridge",
|
"repo": "ops-bridge",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "claude",
|
"flavor": "claude",
|
||||||
"pattern_id": "sp-problem-tool_thrash-tool-bash",
|
"pattern_id": "sp-problem-tool_thrash-tool-bash",
|
||||||
"repo": "state-hub",
|
"repo": "state-hub",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "claude",
|
"flavor": "claude",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "agentic-resources",
|
"repo": "agentic-resources",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:26:26Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "grok",
|
"flavor": "grok",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "agentic-resources",
|
"repo": "agentic-resources",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "claude",
|
"flavor": "claude",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "can-you-assist",
|
"repo": "can-you-assist",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "grok",
|
"flavor": "grok",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "can-you-assist",
|
"repo": "can-you-assist",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "claude",
|
"flavor": "claude",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "ops-bridge",
|
"repo": "ops-bridge",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "grok",
|
"flavor": "grok",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "ops-bridge",
|
"repo": "ops-bridge",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "claude",
|
"flavor": "claude",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "state-hub",
|
"repo": "state-hub",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "grok",
|
"flavor": "grok",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "state-hub",
|
"repo": "state-hub",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "claude",
|
"flavor": "claude",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "the-custodian",
|
"repo": "the-custodian",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"flavor": "grok",
|
"flavor": "grok",
|
||||||
"pattern_id": "sp-success-clean_pass-outcome",
|
"pattern_id": "sp-success-clean_pass-outcome",
|
||||||
"repo": "the-custodian",
|
"repo": "the-custodian",
|
||||||
"status": "proposed",
|
"status": "proposed",
|
||||||
"updated_at": "2026-06-07T13:20:58Z",
|
"updated_at": "2026-06-07T14:25:34Z",
|
||||||
"version": "1.0.0"
|
"version": "1.0.1"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
9
session_memory/retro/__init__.py
Normal file
9
session_memory/retro/__init__.py
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"""Weekly retro (AGENTIC-WP-0010) — the analysis half of the coding retrospection.
|
||||||
|
|
||||||
|
build.py windowed detect + measure -> ranked top-3 suggestions per repo (T01)
|
||||||
|
publish.py publish the retro to the hub read model + local report (T02)
|
||||||
|
__main__.py python -m session_memory.retro (T03)
|
||||||
|
|
||||||
|
Consumed by activity-core's weekly-coding-retro schedule (ACTIVITY-WP-0008) via
|
||||||
|
the ``event_type=coding_retro`` read model.
|
||||||
|
"""
|
||||||
68
session_memory/retro/__main__.py
Normal file
68
session_memory/retro/__main__.py
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
"""Weekly retro entrypoint (AGENTIC-WP-0010 T03).
|
||||||
|
|
||||||
|
python -m session_memory.retro [--window-days 7] [--since D] [--until D]
|
||||||
|
[--publish] [--json]
|
||||||
|
|
||||||
|
Builds the windowed top-3-per-repo retro over the captured sessions, writes a local
|
||||||
|
JSON + markdown report, and (with ``--publish``) posts it to the hub as the
|
||||||
|
``coding_retro`` read model that activity-core's weekly schedule consumes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from ..core.store import Store
|
||||||
|
from ..curate.catalog import Catalog
|
||||||
|
from ..ingest import _expand, load_config
|
||||||
|
from .build import weekly_retro
|
||||||
|
from .publish import publish_to_hub, render_markdown, write_local
|
||||||
|
|
||||||
|
|
||||||
|
def run_retro(config: dict, *, window_days=None, since=None, until=None):
|
||||||
|
s = config.get("store", {})
|
||||||
|
store = Store(_expand(s["db_path"]), _expand(s["blob_dir"]))
|
||||||
|
digests = store.list_digests()
|
||||||
|
store.close()
|
||||||
|
cur = config.get("curate", {})
|
||||||
|
catalog = Catalog(_expand(cur.get("catalog_dir", "session_memory/catalog")))
|
||||||
|
rcfg = config.get("retro", {})
|
||||||
|
return weekly_retro(digests, catalog, since=since, until=until,
|
||||||
|
window_days=window_days or rcfg.get("window_days", 7))
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv=None) -> int:
|
||||||
|
here = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
ap = argparse.ArgumentParser(description="Build (and optionally publish) the weekly coding retro.")
|
||||||
|
ap.add_argument("--config", default=os.path.join(here, "config.toml"))
|
||||||
|
ap.add_argument("--window-days", type=int, default=None)
|
||||||
|
ap.add_argument("--since", default=None)
|
||||||
|
ap.add_argument("--until", default=None)
|
||||||
|
ap.add_argument("--publish", action="store_true", help="post to the hub coding_retro read model")
|
||||||
|
ap.add_argument("--json", action="store_true")
|
||||||
|
args = ap.parse_args(argv)
|
||||||
|
|
||||||
|
config = load_config(args.config)
|
||||||
|
report = run_retro(config, window_days=args.window_days, since=args.since, until=args.until)
|
||||||
|
|
||||||
|
rcfg = config.get("retro", {})
|
||||||
|
write_local(report, _expand(rcfg.get("report_json", "session_memory/retro/last_retro.json")),
|
||||||
|
_expand(rcfg.get("report_md", "session_memory/retro/last_retro.md")))
|
||||||
|
|
||||||
|
published = None
|
||||||
|
if args.publish:
|
||||||
|
published = publish_to_hub(report, base_url=rcfg.get("hub_url", "http://127.0.0.1:8000"))
|
||||||
|
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps({"report": report, "published": published}, indent=2))
|
||||||
|
else:
|
||||||
|
print(render_markdown(report))
|
||||||
|
if args.publish:
|
||||||
|
print(f"\npublished to hub: {published}")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
99
session_memory/retro/build.py
Normal file
99
session_memory/retro/build.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
"""Windowed weekly retro report (AGENTIC-WP-0010 T01).
|
||||||
|
|
||||||
|
Runs the existing detect pipeline over a date window, ranks the recurring problem
|
||||||
|
patterns into **per-repo improvement suggestions** (top 3, cross-flavor first),
|
||||||
|
attaches a recommendation from the Pattern Catalog where one exists, and bundles a
|
||||||
|
fleet measure snapshot for context. Pure function over digests — the entrypoint
|
||||||
|
(T03) handles store/publish.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import collections
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from ..detect.cluster import cluster
|
||||||
|
from ..detect.quality import QualityConfig, filter_real
|
||||||
|
from ..detect.signals import extract_signals
|
||||||
|
from ..measure.metrics import aggregate
|
||||||
|
|
||||||
|
# score at/above which a suggestion is "high" priority even when single-flavor
|
||||||
|
_HIGH_SCORE = 100.0
|
||||||
|
|
||||||
|
|
||||||
|
def _parse(ts: str) -> datetime:
|
||||||
|
return datetime.fromisoformat(ts.replace("Z", "+00:00"))
|
||||||
|
|
||||||
|
|
||||||
|
def _iso(dt: datetime) -> str:
|
||||||
|
return dt.astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
|
||||||
|
def _now() -> datetime:
|
||||||
|
return datetime.now(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Suggestion:
|
||||||
|
repo: str
|
||||||
|
title: str
|
||||||
|
recommendation: str
|
||||||
|
priority: str # high | medium
|
||||||
|
score: float
|
||||||
|
signal_type: str
|
||||||
|
cross_flavor: bool
|
||||||
|
pattern_key: str
|
||||||
|
|
||||||
|
|
||||||
|
def _recommendation(pattern_key: str, locus: str, catalog) -> Optional[str]:
|
||||||
|
if catalog is None:
|
||||||
|
return None
|
||||||
|
sp = catalog.find_for(pattern_key, locus)
|
||||||
|
if sp and sp.resolutions:
|
||||||
|
return sp.resolutions[0].summary
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def weekly_retro(digests: list[dict], catalog=None, *, since: Optional[str] = None,
|
||||||
|
until: Optional[str] = None, window_days: int = 7,
|
||||||
|
max_per_repo: int = 3, min_frequency: int = 2,
|
||||||
|
quality: Optional[QualityConfig] = None) -> dict:
|
||||||
|
"""Build the ranked weekly retro report over a date window."""
|
||||||
|
until_dt = _parse(until) if until else _now()
|
||||||
|
since_dt = _parse(since) if since else until_dt - timedelta(days=window_days)
|
||||||
|
|
||||||
|
windowed = [d for d in digests
|
||||||
|
if d.get("started_at") and since_dt <= _parse(d["started_at"]) < until_dt]
|
||||||
|
real = filter_real(windowed, quality or QualityConfig())
|
||||||
|
|
||||||
|
patterns = cluster(extract_signals(real), min_frequency=min_frequency)
|
||||||
|
|
||||||
|
by_repo: dict[str, list[Suggestion]] = collections.defaultdict(list)
|
||||||
|
for p in patterns:
|
||||||
|
if p.polarity != "problem":
|
||||||
|
continue # improvements come from problems
|
||||||
|
rec = (_recommendation(p.key, p.locus, catalog)
|
||||||
|
or f"Investigate {p.signal_type.replace('_', ' ')} on {p.locus}")
|
||||||
|
priority = "high" if (p.cross_flavor or p.score >= _HIGH_SCORE) else "medium"
|
||||||
|
for repo in (p.repos or ["(unknown)"]):
|
||||||
|
by_repo[repo].append(Suggestion(
|
||||||
|
repo=repo, title=p.title, recommendation=rec, priority=priority,
|
||||||
|
score=p.score, signal_type=p.signal_type, cross_flavor=p.cross_flavor,
|
||||||
|
pattern_key=p.key))
|
||||||
|
|
||||||
|
suggestions: list[Suggestion] = []
|
||||||
|
for repo in sorted(by_repo):
|
||||||
|
items = sorted(by_repo[repo], key=lambda s: -s.score)
|
||||||
|
suggestions.extend(items[:max_per_repo])
|
||||||
|
# cross-flavor first, then by score (global ordering for the report)
|
||||||
|
suggestions.sort(key=lambda s: (not s.cross_flavor, -s.score))
|
||||||
|
|
||||||
|
return {
|
||||||
|
"window": {"since": _iso(since_dt), "until": _iso(until_dt), "days": window_days},
|
||||||
|
"generated_at": _iso(_now()),
|
||||||
|
"n_sessions": len(real),
|
||||||
|
"suggestions": [asdict(s) for s in suggestions],
|
||||||
|
"measure": aggregate(real),
|
||||||
|
}
|
||||||
322
session_memory/retro/last_retro.json
Normal file
322
session_memory/retro/last_retro.json
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
{
|
||||||
|
"generated_at": "2026-06-07T19:30:56Z",
|
||||||
|
"measure": {
|
||||||
|
"error_rate": 0.957,
|
||||||
|
"infra_overhead_share_median": 0.167,
|
||||||
|
"infra_overhead_share_p90": 0.23,
|
||||||
|
"n_sessions": 23,
|
||||||
|
"recurring_error_occurrences": 463,
|
||||||
|
"schema_thrash_sessions": 7,
|
||||||
|
"success_rate": 1.0,
|
||||||
|
"tokens_p50": 250725,
|
||||||
|
"tokens_p90": 901422
|
||||||
|
},
|
||||||
|
"n_sessions": 23,
|
||||||
|
"suggestions": [
|
||||||
|
{
|
||||||
|
"cross_flavor": true,
|
||||||
|
"pattern_key": "problem:recurring_error:make: *** [makefile:<n>: fix-consistency] error <n>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Investigate recurring error on make: *** [makefile:<n>: fix-consistency] error <n>",
|
||||||
|
"repo": "net-kingdom",
|
||||||
|
"score": 54.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "cross-flavor problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:tool_thrash:tool:Bash",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Batch related shell work into one script, not many small Bash calls",
|
||||||
|
"repo": "activity-core",
|
||||||
|
"score": 13128.0,
|
||||||
|
"signal_type": "tool_thrash",
|
||||||
|
"title": "problem: tool thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:tool_thrash:tool:Bash",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Batch related shell work into one script, not many small Bash calls",
|
||||||
|
"repo": "artifact-store",
|
||||||
|
"score": 13128.0,
|
||||||
|
"signal_type": "tool_thrash",
|
||||||
|
"title": "problem: tool thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:tool_thrash:tool:Bash",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Batch related shell work into one script, not many small Bash calls",
|
||||||
|
"repo": "citation-evidence",
|
||||||
|
"score": 13128.0,
|
||||||
|
"signal_type": "tool_thrash",
|
||||||
|
"title": "problem: tool thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:tool_thrash:tool:Bash",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Batch related shell work into one script, not many small Bash calls",
|
||||||
|
"repo": "infospace-bench",
|
||||||
|
"score": 13128.0,
|
||||||
|
"signal_type": "tool_thrash",
|
||||||
|
"title": "problem: tool thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:tool_thrash:tool:Bash",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Batch related shell work into one script, not many small Bash calls",
|
||||||
|
"repo": "railiance-apps",
|
||||||
|
"score": 13128.0,
|
||||||
|
"signal_type": "tool_thrash",
|
||||||
|
"title": "problem: tool thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:tool_thrash:tool:Bash",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Batch related shell work into one script, not many small Bash calls",
|
||||||
|
"repo": "state-hub",
|
||||||
|
"score": 13128.0,
|
||||||
|
"signal_type": "tool_thrash",
|
||||||
|
"title": "problem: tool thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:schema_thrash:schema_load",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Load the tool schemas you'll need once, up front",
|
||||||
|
"repo": "activity-core",
|
||||||
|
"score": 441.0,
|
||||||
|
"signal_type": "schema_thrash",
|
||||||
|
"title": "problem: schema thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:schema_thrash:schema_load",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Load the tool schemas you'll need once, up front",
|
||||||
|
"repo": "citation-evidence",
|
||||||
|
"score": 441.0,
|
||||||
|
"signal_type": "schema_thrash",
|
||||||
|
"title": "problem: schema thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:schema_thrash:schema_load",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Load the tool schemas you'll need once, up front",
|
||||||
|
"repo": "flex-auth",
|
||||||
|
"score": 441.0,
|
||||||
|
"signal_type": "schema_thrash",
|
||||||
|
"title": "problem: schema thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:schema_thrash:schema_load",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Load the tool schemas you'll need once, up front",
|
||||||
|
"repo": "infospace-bench",
|
||||||
|
"score": 441.0,
|
||||||
|
"signal_type": "schema_thrash",
|
||||||
|
"title": "problem: schema thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:schema_thrash:schema_load",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Load the tool schemas you'll need once, up front",
|
||||||
|
"repo": "ops-bridge",
|
||||||
|
"score": 441.0,
|
||||||
|
"signal_type": "schema_thrash",
|
||||||
|
"title": "problem: schema thrash"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "activity-core",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "citation-evidence",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "infospace-bench",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "issue-facade",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "railiance-apps",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "state-hub",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "the-custodian",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has not been read yet. read it first before writing to it.<<path>>",
|
||||||
|
"priority": "high",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "vergabe-teilnahme",
|
||||||
|
"score": 290.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has been modified since read, either by the user or by a linter. read it again before attempting to write it.<<path>>",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "artifact-store",
|
||||||
|
"score": 78.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has been modified since read, either by the user or by a linter. read it again before attempting to write it.<<path>>",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "issue-facade",
|
||||||
|
"score": 78.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has been modified since read, either by the user or by a linter. read it again before attempting to write it.<<path>>",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "railiance-apps",
|
||||||
|
"score": 78.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<tool_use_error>file has been modified since read, either by the user or by a linter. read it again before attempting to write it.<<path>>",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Read the file (or the region you'll touch) before Edit/Write",
|
||||||
|
"repo": "state-hub",
|
||||||
|
"score": 78.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:budget_overrun:tokens",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Read narrowly \u2014 target the region you need, not whole large files",
|
||||||
|
"repo": "artifact-store",
|
||||||
|
"score": 50.55,
|
||||||
|
"signal_type": "budget_overrun",
|
||||||
|
"title": "problem: budget overrun"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:{",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Investigate recurring error on {",
|
||||||
|
"repo": "vergabe-teilnahme",
|
||||||
|
"score": 12.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:found <n> errors (<n> fixed, <n> remaining).",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Investigate recurring error on found <n> errors (<n> fixed, <n> remaining).",
|
||||||
|
"repo": "ops-bridge",
|
||||||
|
"score": 10.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:(note: edit also tried swapping \\uxxxx escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. re-read the file a",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Investigate recurring error on (note: edit also tried swapping \\uxxxx escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. re-read the file a",
|
||||||
|
"repo": "net-kingdom",
|
||||||
|
"score": 6.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:found <n> error (<n> fixed, <n> remaining).",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Investigate recurring error on found <n> error (<n> fixed, <n> remaining).",
|
||||||
|
"repo": "ops-bridge",
|
||||||
|
"score": 6.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cross_flavor": false,
|
||||||
|
"pattern_key": "problem:recurring_error:<n> failed, <n> passed in <n>.00s",
|
||||||
|
"priority": "medium",
|
||||||
|
"recommendation": "Investigate recurring error on <n> failed, <n> passed in <n>.00s",
|
||||||
|
"repo": "agentic-resources",
|
||||||
|
"score": 4.0,
|
||||||
|
"signal_type": "recurring_error",
|
||||||
|
"title": "problem: recurring error"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"window": {
|
||||||
|
"days": 30,
|
||||||
|
"since": "2026-05-08T19:30:56Z",
|
||||||
|
"until": "2026-06-07T19:30:56Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
39
session_memory/retro/last_retro.md
Normal file
39
session_memory/retro/last_retro.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Weekly Coding Retro (2026-05-08 → 2026-06-07)
|
||||||
|
_23 real sessions · generated 2026-06-07T19:30:56Z_
|
||||||
|
|
||||||
|
## Top improvement suggestions (cross-flavor first, ≤3 per repo)
|
||||||
|
- **net-kingdom** (high, score=54.0) [CROSS-FLAVOR]: cross-flavor problem: recurring error — Investigate recurring error on make: *** [makefile:<n>: fix-consistency] error <n>
|
||||||
|
- **activity-core** (high, score=13128.0): problem: tool thrash — Batch related shell work into one script, not many small Bash calls
|
||||||
|
- **artifact-store** (high, score=13128.0): problem: tool thrash — Batch related shell work into one script, not many small Bash calls
|
||||||
|
- **citation-evidence** (high, score=13128.0): problem: tool thrash — Batch related shell work into one script, not many small Bash calls
|
||||||
|
- **infospace-bench** (high, score=13128.0): problem: tool thrash — Batch related shell work into one script, not many small Bash calls
|
||||||
|
- **railiance-apps** (high, score=13128.0): problem: tool thrash — Batch related shell work into one script, not many small Bash calls
|
||||||
|
- **state-hub** (high, score=13128.0): problem: tool thrash — Batch related shell work into one script, not many small Bash calls
|
||||||
|
- **activity-core** (high, score=441.0): problem: schema thrash — Load the tool schemas you'll need once, up front
|
||||||
|
- **citation-evidence** (high, score=441.0): problem: schema thrash — Load the tool schemas you'll need once, up front
|
||||||
|
- **flex-auth** (high, score=441.0): problem: schema thrash — Load the tool schemas you'll need once, up front
|
||||||
|
- **infospace-bench** (high, score=441.0): problem: schema thrash — Load the tool schemas you'll need once, up front
|
||||||
|
- **ops-bridge** (high, score=441.0): problem: schema thrash — Load the tool schemas you'll need once, up front
|
||||||
|
- **activity-core** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **citation-evidence** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **infospace-bench** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **issue-facade** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **railiance-apps** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **state-hub** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **the-custodian** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **vergabe-teilnahme** (high, score=290.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **artifact-store** (medium, score=78.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **issue-facade** (medium, score=78.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **railiance-apps** (medium, score=78.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **state-hub** (medium, score=78.0): problem: recurring error — Read the file (or the region you'll touch) before Edit/Write
|
||||||
|
- **artifact-store** (medium, score=50.55): problem: budget overrun — Read narrowly — target the region you need, not whole large files
|
||||||
|
- **vergabe-teilnahme** (medium, score=12.0): problem: recurring error — Investigate recurring error on {
|
||||||
|
- **ops-bridge** (medium, score=10.0): problem: recurring error — Investigate recurring error on found <n> errors (<n> fixed, <n> remaining).
|
||||||
|
- **net-kingdom** (medium, score=6.0): problem: recurring error — Investigate recurring error on (note: edit also tried swapping \uxxxx escapes and their characters; neither form matched, so the mismatch is likely elsewhere in old_string. re-read the file a
|
||||||
|
- **ops-bridge** (medium, score=6.0): problem: recurring error — Investigate recurring error on found <n> error (<n> fixed, <n> remaining).
|
||||||
|
- **agentic-resources** (medium, score=4.0): problem: recurring error — Investigate recurring error on <n> failed, <n> passed in <n>.00s
|
||||||
|
|
||||||
|
## Fleet snapshot
|
||||||
|
- infra-overhead median: 0.167
|
||||||
|
- error rate: 0.957 · schema-thrash: 7
|
||||||
|
- success rate: 1.0 · tokens p50: 250725
|
||||||
78
session_memory/retro/publish.py
Normal file
78
session_memory/retro/publish.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
"""Publish the weekly retro (AGENTIC-WP-0010 T02).
|
||||||
|
|
||||||
|
The retro is published to the State Hub as a **read model** — a progress event of
|
||||||
|
``event_type=coding_retro`` whose ``detail`` carries the structured report. This is
|
||||||
|
exactly how ``daily-triage-report`` surfaces, and it is what activity-core's
|
||||||
|
``coding_retro`` resolver (ACTIVITY-WP-0008) reads. A local JSON + markdown report
|
||||||
|
is always written; the hub publish is best-effort and **degrades gracefully** when
|
||||||
|
the hub is unreachable.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import urllib.request
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
DEFAULT_HUB = "http://127.0.0.1:8000"
|
||||||
|
|
||||||
|
|
||||||
|
def render_markdown(report: dict) -> str:
|
||||||
|
w = report.get("window", {})
|
||||||
|
lines = [
|
||||||
|
f"# Weekly Coding Retro ({w.get('since', '')[:10]} → {w.get('until', '')[:10]})",
|
||||||
|
f"_{report.get('n_sessions', 0)} real sessions · generated {report.get('generated_at', '')}_",
|
||||||
|
"",
|
||||||
|
"## Top improvement suggestions (cross-flavor first, ≤3 per repo)",
|
||||||
|
]
|
||||||
|
if not report.get("suggestions"):
|
||||||
|
lines.append("- (no recurring problems above threshold this week)")
|
||||||
|
for s in report.get("suggestions", []):
|
||||||
|
flag = " [CROSS-FLAVOR]" if s.get("cross_flavor") else ""
|
||||||
|
lines.append(f"- **{s['repo']}** ({s['priority']}, score={s['score']}){flag}: "
|
||||||
|
f"{s['title']} — {s['recommendation']}")
|
||||||
|
m = report.get("measure", {})
|
||||||
|
lines += ["", "## Fleet snapshot",
|
||||||
|
f"- infra-overhead median: {m.get('infra_overhead_share_median')}",
|
||||||
|
f"- error rate: {m.get('error_rate')} · schema-thrash: {m.get('schema_thrash_sessions')}",
|
||||||
|
f"- success rate: {m.get('success_rate')} · tokens p50: {m.get('tokens_p50')}"]
|
||||||
|
return "\n".join(lines)
|
||||||
|
|
||||||
|
|
||||||
|
def write_local(report: dict, json_path: str, md_path: Optional[str] = None) -> None:
|
||||||
|
os.makedirs(os.path.dirname(json_path) or ".", exist_ok=True)
|
||||||
|
with open(json_path, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump(report, fh, indent=2, sort_keys=True)
|
||||||
|
fh.write("\n")
|
||||||
|
if md_path:
|
||||||
|
with open(md_path, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(render_markdown(report))
|
||||||
|
fh.write("\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _http_post(url: str, payload: dict) -> None:
|
||||||
|
req = urllib.request.Request(url, data=json.dumps(payload).encode(),
|
||||||
|
headers={"Content-Type": "application/json"}, method="POST")
|
||||||
|
with urllib.request.urlopen(req, timeout=10) as r:
|
||||||
|
r.read()
|
||||||
|
|
||||||
|
|
||||||
|
def publish_to_hub(report: dict, *, base_url: str = DEFAULT_HUB,
|
||||||
|
poster: Optional[Callable[[str, dict], None]] = None) -> bool:
|
||||||
|
"""POST the retro as an event_type=coding_retro progress event. Best-effort."""
|
||||||
|
poster = poster or _http_post
|
||||||
|
n = report.get("n_sessions", 0)
|
||||||
|
k = len(report.get("suggestions", []))
|
||||||
|
payload = {
|
||||||
|
"event_type": "coding_retro",
|
||||||
|
"author": "helix-forge",
|
||||||
|
"summary": f"Weekly coding retro: {k} ranked suggestions across "
|
||||||
|
f"{report.get('window', {}).get('days', 7)} days ({n} sessions).",
|
||||||
|
"detail": report,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
poster(f"{base_url.rstrip('/')}/progress/", payload)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
62
tests/test_catalog_covers.py
Normal file
62
tests/test_catalog_covers.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""find_for / covers tests (AGENTIC-WP-0010 follow-up)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from session_memory.curate.catalog import Catalog # noqa: E402
|
||||||
|
from session_memory.curate.schema import ( # noqa: E402
|
||||||
|
Provenance,
|
||||||
|
Resolution,
|
||||||
|
SolutionPattern,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _pattern(pid, src, covers=None, name="P"):
|
||||||
|
return SolutionPattern(
|
||||||
|
id=pid, name=name, version="1.0.0", polarity="problem", problem="p",
|
||||||
|
resolutions=[Resolution(summary="do x")],
|
||||||
|
provenance=Provenance(source_key=src), covers=covers or [])
|
||||||
|
|
||||||
|
|
||||||
|
def test_covers_round_trips(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path))
|
||||||
|
cat.upsert(_pattern("sp-a", "problem:file_not_read:edit",
|
||||||
|
covers=["file has not been read"]))
|
||||||
|
assert cat.load("sp-a").covers == ["file has not been read"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_for_exact_key(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path))
|
||||||
|
cat.upsert(_pattern(SolutionPattern.make_id("problem:retry_storm:retries"),
|
||||||
|
"problem:retry_storm:retries"))
|
||||||
|
got = cat.find_for("problem:retry_storm:retries")
|
||||||
|
assert got is not None and got.id == "sp-problem-retry_storm-retries"
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_for_covers_match(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path))
|
||||||
|
cat.upsert(_pattern("sp-rbe", "problem:file_not_read:edit",
|
||||||
|
covers=["file has not been read", "modified since read"]))
|
||||||
|
# a recurring_error signal with a different key but matching fingerprint locus
|
||||||
|
got = cat.find_for(
|
||||||
|
"problem:recurring_error:<tool_use_error>file has not been read yet...",
|
||||||
|
locus="<tool_use_error>file has not been read yet. read it first...")
|
||||||
|
assert got is not None and got.id == "sp-rbe"
|
||||||
|
|
||||||
|
|
||||||
|
def test_find_for_no_match_returns_none(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path))
|
||||||
|
cat.upsert(_pattern("sp-rbe", "problem:file_not_read:edit",
|
||||||
|
covers=["file has not been read"]))
|
||||||
|
assert cat.find_for("problem:recurring_error:some unrelated error") is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_covers_change_versions(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path))
|
||||||
|
cat.upsert(_pattern("sp-a", "problem:x:y"))
|
||||||
|
p = cat.load("sp-a")
|
||||||
|
p.covers = ["new coverage"]
|
||||||
|
assert cat.upsert(p) == "versioned" # covers is substantive content
|
||||||
|
assert cat.load("sp-a").version == "1.0.1"
|
||||||
78
tests/test_digest_lookup.py
Normal file
78
tests/test_digest_lookup.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
"""digest_lookup entrypoint tests (AGENTIC-WP-0011 T03)."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from session_memory.core.store import Store # noqa: E402
|
||||||
|
from session_memory.digest_lookup import lookup_digest, main, resolve_store_paths # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _write_config(tmp_path) -> str:
|
||||||
|
store = tmp_path / ".store"
|
||||||
|
toml = tmp_path / "config.toml"
|
||||||
|
toml.write_text(
|
||||||
|
f'[store]\ndb_path = "{store / "m.db"}"\nblob_dir = "{store / "blobs"}"\n'
|
||||||
|
f'cursor = "{store / "c.json"}"\n')
|
||||||
|
return str(toml), str(store)
|
||||||
|
|
||||||
|
|
||||||
|
def _seed(store_dir, uid="claude:test-uid"):
|
||||||
|
st = Store(os.path.join(store_dir, "m.db"), os.path.join(store_dir, "blobs"))
|
||||||
|
st.write_digest(uid, {
|
||||||
|
"session_uid": uid,
|
||||||
|
"flavor": "claude",
|
||||||
|
"repo": "agentic-resources",
|
||||||
|
"outcome": "success",
|
||||||
|
"started_at": "2026-06-19T10:00:00Z",
|
||||||
|
"ended_at": "2026-06-19T11:00:00Z",
|
||||||
|
"cost": {"input_tokens": 100, "output_tokens": 25},
|
||||||
|
"tool_histogram": {"Bash": 10, "Edit": 5},
|
||||||
|
})
|
||||||
|
st.close()
|
||||||
|
return uid
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_store_paths_from_config(tmp_path):
|
||||||
|
cfg_path, store_dir = _write_config(tmp_path)
|
||||||
|
db, blob = resolve_store_paths(config_path=cfg_path)
|
||||||
|
assert db.endswith("m.db")
|
||||||
|
assert blob.endswith("blobs")
|
||||||
|
assert store_dir in db
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_store_paths_from_env(tmp_path, monkeypatch):
|
||||||
|
db = tmp_path / "custom" / "mem.db"
|
||||||
|
db.parent.mkdir(parents=True)
|
||||||
|
monkeypatch.setenv("HELIX_STORE_DB", str(db))
|
||||||
|
resolved_db, blob = resolve_store_paths()
|
||||||
|
assert resolved_db == str(db)
|
||||||
|
assert blob == str(tmp_path / "custom" / "blobs")
|
||||||
|
|
||||||
|
|
||||||
|
def test_lookup_digest_found_and_missing(tmp_path):
|
||||||
|
cfg_path, store_dir = _write_config(tmp_path)
|
||||||
|
uid = _seed(store_dir)
|
||||||
|
found = lookup_digest(uid, config_path=cfg_path)
|
||||||
|
assert found is not None and found["outcome"] == "success"
|
||||||
|
assert lookup_digest("claude:missing", config_path=cfg_path) is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_json_success(tmp_path, capsys):
|
||||||
|
cfg_path, store_dir = _write_config(tmp_path)
|
||||||
|
uid = _seed(store_dir)
|
||||||
|
rc = main(["--config", cfg_path, uid, "--json"])
|
||||||
|
assert rc == 0
|
||||||
|
data = json.loads(capsys.readouterr().out)
|
||||||
|
assert data["session_uid"] == uid
|
||||||
|
assert data["repo"] == "agentic-resources"
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_not_found(tmp_path, capsys):
|
||||||
|
cfg_path, store_dir = _write_config(tmp_path)
|
||||||
|
_seed(store_dir)
|
||||||
|
rc = main(["--config", cfg_path, "claude:missing"])
|
||||||
|
assert rc == 1
|
||||||
|
assert "not found" in capsys.readouterr().err.lower()
|
||||||
106
tests/test_retro_build.py
Normal file
106
tests/test_retro_build.py
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
"""Weekly retro report tests (AGENTIC-WP-0010 T01)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from session_memory.curate.catalog import Catalog # noqa: E402
|
||||||
|
from session_memory.curate.schema import Resolution, SolutionPattern # noqa: E402
|
||||||
|
from session_memory.retro.build import weekly_retro # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _digest(uid, repo, ts, flavor="claude", retries=5):
|
||||||
|
return {
|
||||||
|
"session_uid": uid, "flavor": flavor, "repo": repo, "outcome": "fail",
|
||||||
|
"started_at": ts, "event_count": 40,
|
||||||
|
"first_prompt": "Fix the failing build and retry the suite",
|
||||||
|
"cost": {"input_tokens": 100, "output_tokens": 10},
|
||||||
|
"tool_histogram": {"Bash": 20, "Edit": 12, "Read": 8},
|
||||||
|
"markers": {"errors": 0, "retries": retries, "test_runs": 0},
|
||||||
|
"error_snippets": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_window_excludes_old_sessions():
|
||||||
|
digs = [
|
||||||
|
_digest("claude:a", "r1", "2026-06-01T10:00:00Z"),
|
||||||
|
_digest("claude:b", "r1", "2026-06-02T10:00:00Z"),
|
||||||
|
_digest("claude:old", "r1", "2026-01-01T10:00:00Z"), # outside window
|
||||||
|
]
|
||||||
|
r = weekly_retro(digs, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
assert r["n_sessions"] == 2
|
||||||
|
assert r["window"]["days"] == 7
|
||||||
|
|
||||||
|
|
||||||
|
def test_retry_storm_becomes_suggestion():
|
||||||
|
digs = [_digest(f"claude:{i}", "r1", "2026-06-0{}T10:00:00Z".format(i + 1))
|
||||||
|
for i in range(2)]
|
||||||
|
r = weekly_retro(digs, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
s = r["suggestions"]
|
||||||
|
assert s and s[0]["repo"] == "r1"
|
||||||
|
assert s[0]["signal_type"] == "retry_storm"
|
||||||
|
assert "Investigate" in s[0]["recommendation"] # no catalog -> default
|
||||||
|
|
||||||
|
|
||||||
|
def test_recommendation_from_catalog(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path / "catalog"))
|
||||||
|
key = "problem:retry_storm:retries"
|
||||||
|
cat.upsert(SolutionPattern(
|
||||||
|
id=SolutionPattern.make_id(key), name="Retry storm", version="1.0.0",
|
||||||
|
polarity="problem", problem="repeated retries",
|
||||||
|
resolutions=[Resolution(summary="Stop and diagnose before retrying")]))
|
||||||
|
digs = [_digest(f"claude:{i}", "r1", "2026-06-0{}T10:00:00Z".format(i + 1)) for i in range(2)]
|
||||||
|
r = weekly_retro(digs, catalog=cat, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
assert r["suggestions"][0]["recommendation"] == "Stop and diagnose before retrying"
|
||||||
|
|
||||||
|
|
||||||
|
def test_recurring_error_inherits_recommendation_via_covers(tmp_path):
|
||||||
|
cat = Catalog(str(tmp_path / "catalog"))
|
||||||
|
cat.upsert(SolutionPattern(
|
||||||
|
id="sp-rbe", name="Read before edit", version="1.0.0", polarity="problem",
|
||||||
|
problem="edit before read",
|
||||||
|
resolutions=[Resolution(summary="Read the file first before Edit/Write")],
|
||||||
|
covers=["file has not been read"]))
|
||||||
|
digs = []
|
||||||
|
for i in range(2):
|
||||||
|
d = _digest(f"claude:{i}", "r1", "2026-06-0{}T10:00:00Z".format(i + 1))
|
||||||
|
d["error_snippets"] = [{
|
||||||
|
"fingerprint": "<tool_use_error>file has not been read yet. read it first...",
|
||||||
|
"sample": "File has not been read yet", "count": 2, "tool": "Edit"}]
|
||||||
|
digs.append(d)
|
||||||
|
r = weekly_retro(digs, catalog=cat, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
rec_err = [s for s in r["suggestions"] if s["signal_type"] == "recurring_error"]
|
||||||
|
assert rec_err, "expected a recurring_error suggestion"
|
||||||
|
assert rec_err[0]["recommendation"] == "Read the file first before Edit/Write"
|
||||||
|
|
||||||
|
|
||||||
|
def test_caps_three_per_repo():
|
||||||
|
# five distinct problem signals in one repo -> capped at 3
|
||||||
|
digs = []
|
||||||
|
for i in range(2):
|
||||||
|
d = _digest(f"claude:{i}", "r1", "2026-06-0{}T10:00:00Z".format(i + 1))
|
||||||
|
d["markers"] = {"errors": 5, "retries": 5, "test_runs": 0, "human_interventions": 0}
|
||||||
|
d["tool_histogram"] = {"Bash": 120, "ToolSearch": 9,
|
||||||
|
"mcp__state-hub__x": 30, "Edit": 5}
|
||||||
|
d["outcome"] = "abandoned"
|
||||||
|
digs.append(d)
|
||||||
|
r = weekly_retro(digs, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
per_repo = [s for s in r["suggestions"] if s["repo"] == "r1"]
|
||||||
|
assert len(per_repo) <= 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_cross_flavor_ranks_first():
|
||||||
|
digs = [
|
||||||
|
_digest("claude:a", "r1", "2026-06-01T10:00:00Z", flavor="claude"),
|
||||||
|
_digest("grok:b", "r2", "2026-06-02T10:00:00Z", flavor="grok"),
|
||||||
|
]
|
||||||
|
r = weekly_retro(digs, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
assert r["suggestions"][0]["cross_flavor"] is True
|
||||||
|
assert r["suggestions"][0]["priority"] == "high"
|
||||||
|
|
||||||
|
|
||||||
|
def test_includes_measure_snapshot():
|
||||||
|
digs = [_digest(f"claude:{i}", "r1", "2026-06-0{}T10:00:00Z".format(i + 1)) for i in range(2)]
|
||||||
|
r = weekly_retro(digs, since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
assert r["measure"]["n_sessions"] == 2
|
||||||
63
tests/test_retro_entrypoint.py
Normal file
63
tests/test_retro_entrypoint.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""Retro entrypoint tests (AGENTIC-WP-0010 T03)."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from session_memory.core.store import Store # noqa: E402
|
||||||
|
from session_memory.retro.__main__ import main, run_retro # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def _digest(uid, repo, ts, retries=5):
|
||||||
|
return {
|
||||||
|
"session_uid": uid, "flavor": "claude", "repo": repo, "outcome": "fail",
|
||||||
|
"started_at": ts, "event_count": 40,
|
||||||
|
"first_prompt": "Fix the failing build and retry the suite repeatedly",
|
||||||
|
"cost": {"input_tokens": 100, "output_tokens": 10},
|
||||||
|
"tool_histogram": {"Bash": 20, "Edit": 12, "Read": 8},
|
||||||
|
"markers": {"errors": 0, "retries": retries, "test_runs": 0},
|
||||||
|
"error_snippets": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _config(tmp_path):
|
||||||
|
store = tmp_path / ".store"
|
||||||
|
toml = tmp_path / "config.toml"
|
||||||
|
toml.write_text(
|
||||||
|
f'[store]\ndb_path="{store / "m.db"}"\nblob_dir="{store / "blobs"}"\ncursor="{store / "c.json"}"\n'
|
||||||
|
f'[curate]\ncatalog_dir="{tmp_path / "catalog"}"\n'
|
||||||
|
f'[retro]\nwindow_days=7\nreport_json="{tmp_path / "r.json"}"\nreport_md="{tmp_path / "r.md"}"\n')
|
||||||
|
st = Store(str(store / "m.db"), str(store / "blobs"))
|
||||||
|
st.write_digest("claude:a", _digest("claude:a", "r1", "2026-06-01T10:00:00Z"))
|
||||||
|
st.write_digest("claude:b", _digest("claude:b", "r1", "2026-06-02T10:00:00Z"))
|
||||||
|
st.close()
|
||||||
|
return str(toml), tmp_path
|
||||||
|
|
||||||
|
|
||||||
|
def test_run_retro_over_store(tmp_path):
|
||||||
|
from session_memory.ingest import load_config
|
||||||
|
cfg_path, _ = _config(tmp_path)
|
||||||
|
rep = run_retro(load_config(cfg_path), since="2026-05-30T00:00:00Z", until="2026-06-08T00:00:00Z")
|
||||||
|
assert rep["n_sessions"] == 2
|
||||||
|
assert rep["suggestions"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_writes_report_files(tmp_path, capsys):
|
||||||
|
cfg_path, tp = _config(tmp_path)
|
||||||
|
rc = main(["--config", cfg_path, "--since", "2026-05-30T00:00:00Z",
|
||||||
|
"--until", "2026-06-08T00:00:00Z"])
|
||||||
|
assert rc == 0
|
||||||
|
assert os.path.exists(str(tp / "r.json")) and os.path.exists(str(tp / "r.md"))
|
||||||
|
assert "Weekly Coding Retro" in capsys.readouterr().out
|
||||||
|
|
||||||
|
|
||||||
|
def test_main_json(tmp_path, capsys):
|
||||||
|
cfg_path, _ = _config(tmp_path)
|
||||||
|
rc = main(["--config", cfg_path, "--since", "2026-05-30T00:00:00Z",
|
||||||
|
"--until", "2026-06-08T00:00:00Z", "--json"])
|
||||||
|
assert rc == 0
|
||||||
|
data = json.loads(capsys.readouterr().out)
|
||||||
|
assert data["report"]["n_sessions"] == 2
|
||||||
|
assert data["published"] is None # no --publish
|
||||||
62
tests/test_retro_publish.py
Normal file
62
tests/test_retro_publish.py
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
"""Retro publish tests (AGENTIC-WP-0010 T02)."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from session_memory.retro.publish import ( # noqa: E402
|
||||||
|
publish_to_hub,
|
||||||
|
render_markdown,
|
||||||
|
write_local,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _report():
|
||||||
|
return {
|
||||||
|
"window": {"since": "2026-06-01T00:00:00Z", "until": "2026-06-08T00:00:00Z", "days": 7},
|
||||||
|
"generated_at": "2026-06-08T19:00:00Z", "n_sessions": 12,
|
||||||
|
"suggestions": [
|
||||||
|
{"repo": "state-hub", "title": "schema thrash", "recommendation": "front-load schemas",
|
||||||
|
"priority": "high", "score": 632.0, "cross_flavor": False, "signal_type": "schema_thrash"},
|
||||||
|
],
|
||||||
|
"measure": {"infra_overhead_share_median": 0.117, "error_rate": 0.96,
|
||||||
|
"schema_thrash_sessions": 8, "success_rate": 1.0, "tokens_p50": 250725},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_render_markdown():
|
||||||
|
md = render_markdown(_report())
|
||||||
|
assert "Weekly Coding Retro" in md
|
||||||
|
assert "**state-hub**" in md and "front-load schemas" in md
|
||||||
|
assert "infra-overhead median: 0.117" in md
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_local_json_and_md(tmp_path):
|
||||||
|
jp = str(tmp_path / "out" / "retro.json")
|
||||||
|
mp = str(tmp_path / "out" / "retro.md")
|
||||||
|
write_local(_report(), jp, mp)
|
||||||
|
assert json.load(open(jp))["n_sessions"] == 12
|
||||||
|
assert "Weekly Coding Retro" in open(mp).read()
|
||||||
|
|
||||||
|
|
||||||
|
def test_publish_calls_poster_with_coding_retro_event():
|
||||||
|
captured = {}
|
||||||
|
|
||||||
|
def poster(url, payload):
|
||||||
|
captured["url"] = url
|
||||||
|
captured["payload"] = payload
|
||||||
|
|
||||||
|
ok = publish_to_hub(_report(), base_url="http://hub", poster=poster)
|
||||||
|
assert ok is True
|
||||||
|
assert captured["url"] == "http://hub/progress/"
|
||||||
|
assert captured["payload"]["event_type"] == "coding_retro"
|
||||||
|
assert captured["payload"]["detail"]["n_sessions"] == 12
|
||||||
|
|
||||||
|
|
||||||
|
def test_publish_degrades_gracefully_on_failure():
|
||||||
|
def boom(url, payload):
|
||||||
|
raise OSError("hub down")
|
||||||
|
|
||||||
|
assert publish_to_hub(_report(), poster=boom) is False
|
||||||
76
workplans/AGENTIC-WP-0010-weekly-retro.md
Normal file
76
workplans/AGENTIC-WP-0010-weekly-retro.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
---
|
||||||
|
id: AGENTIC-WP-0010
|
||||||
|
type: workplan
|
||||||
|
title: "Coding Session Memory — Weekly Retro entrypoint + hub publish"
|
||||||
|
domain: helix_forge
|
||||||
|
repo: agentic-resources
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: helix-forge
|
||||||
|
created: "2026-06-07"
|
||||||
|
updated: "2026-06-07"
|
||||||
|
state_hub_workstream_id: "6b9816e4-65bc-4fc7-b8e1-33f4edd51e7a"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Coding Session Memory — Weekly Retro entrypoint + hub publish
|
||||||
|
|
||||||
|
The **analysis half** of a weekly coding retrospection. A windowed retro runs
|
||||||
|
detect + measure over the previous week, ranks the **top-3 improvement
|
||||||
|
suggestions per repo** (impact × frequency, cross-flavor first; recommendations
|
||||||
|
pulled from the Pattern Catalog), and **publishes the ranked result to the State
|
||||||
|
Hub as a read model** (an `event_type=coding_retro` progress event, mirroring how
|
||||||
|
`daily-triage-report` publishes).
|
||||||
|
|
||||||
|
This is the dependency that activity-core's weekly schedule consumes
|
||||||
|
(`activity-wp-0008` — *Weekly Coding Retrospection schedule*). Keeping the analysis
|
||||||
|
here and publishing to the hub keeps activity-core decoupled from the
|
||||||
|
workstation-local session store.
|
||||||
|
|
||||||
|
## Windowed Weekly Retro Report (top-3 per repo)
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-0010-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "34d30250-c0d3-4837-81c7-1c858c2ee801"
|
||||||
|
```
|
||||||
|
|
||||||
|
`retro/build.py`: window digests by date (last N days), run
|
||||||
|
`extract_signals` + `cluster` over the window, explode problem patterns per repo,
|
||||||
|
rank by score and cap at **3 per repo**. Attach a recommendation per suggestion
|
||||||
|
from the Pattern Catalog (lookup by pattern key → first resolution) with a sensible
|
||||||
|
default. Include a fleet measure snapshot for context. Pure function over digests;
|
||||||
|
unit-tested.
|
||||||
|
|
||||||
|
## Publish Retro to the Hub + Local Report
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-0010-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "cbe1288a-ce51-48c0-b741-adf4a6cbce3a"
|
||||||
|
```
|
||||||
|
|
||||||
|
Publish the ranked retro to the State Hub as a read model: POST a progress event
|
||||||
|
(`event_type=coding_retro`) with the structured report (`suggestions[]`, window,
|
||||||
|
`generated_at`) in `detail`. Also write a local JSON + markdown report. **Graceful
|
||||||
|
degrade** when the hub is unreachable (write local, skip publish). Hub URL under
|
||||||
|
`[retro]` in `config.toml`.
|
||||||
|
|
||||||
|
## Retro Entrypoint + Tests + Live Verify
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-0010-T03
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "af540220-58dd-4cf5-a9dc-6db4b995fa08"
|
||||||
|
```
|
||||||
|
|
||||||
|
`python -m session_memory.retro [--window-days 7] [--publish] [--json]`: windowed
|
||||||
|
retro → ranked top-3 per repo → optional hub publish + local report. Document in
|
||||||
|
`session_memory/README.md`. Live verify over the real local sessions. After
|
||||||
|
workplan updates, notify the operator to run from `~/state-hub`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make fix-consistency REPO=agentic-resources
|
||||||
|
```
|
||||||
99
workplans/AGENTIC-WP-0011-kaizen-correlation-followup.md
Normal file
99
workplans/AGENTIC-WP-0011-kaizen-correlation-followup.md
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
---
|
||||||
|
id: AGENTIC-WP-0011
|
||||||
|
type: workplan
|
||||||
|
title: "Helix Forge ↔ kaizen-agentic correlation — doc links and session-close contract"
|
||||||
|
domain: helix_forge
|
||||||
|
repo: agentic-resources
|
||||||
|
status: finished
|
||||||
|
owner: codex
|
||||||
|
topic_slug: helix-forge
|
||||||
|
created: "2026-06-19"
|
||||||
|
updated: "2026-06-21"
|
||||||
|
state_hub_workstream_id: "a689a145-645d-4ed4-a16c-00b75263a2c4"
|
||||||
|
---
|
||||||
|
|
||||||
|
# Helix Forge ↔ kaizen-agentic Correlation — Doc Links and Session-Close Contract
|
||||||
|
|
||||||
|
**Coordination trigger:** State Hub inbox from `kaizen-agentic` (2026-06-15) after
|
||||||
|
KAIZEN-WP-0004 — ADR-004 correlation layer is live on the project-metrics side.
|
||||||
|
This repo already has a partial link in `docs/DESIGN-session-memory.md` §11 (commit
|
||||||
|
`a66d502`). Remaining work is bidirectional surfacing, a session-close env-export
|
||||||
|
convention for agents using both layers, and a stable read-path for
|
||||||
|
`kaizen-agentic metrics correlate`.
|
||||||
|
|
||||||
|
**Boundary:** link-by-reference only — no ingestion or write path from kaizen-agentic
|
||||||
|
into Helix Forge. Authoritative cross-repo contract stays in kaizen-agentic:
|
||||||
|
`docs/integrations/helix-forge-correlation.md`.
|
||||||
|
|
||||||
|
## Bidirectional Doc Links and Stale Design Footer
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-0011-T01
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "616ec197-58ce-4de0-bb7b-1c4a3f9a1e24"
|
||||||
|
```
|
||||||
|
|
||||||
|
Complete the bidirectional documentation surface requested by kaizen-agentic:
|
||||||
|
|
||||||
|
- Add a **Project metrics correlation** subsection to `docs/PRD-helix-forge.md`
|
||||||
|
(ecosystem integration / downstream consumers) linking to DESIGN §11 and the
|
||||||
|
kaizen contract doc.
|
||||||
|
- Add a short **Correlation with kaizen-agentic** section to
|
||||||
|
`session_memory/README.md` (two-layer model, link to contract, no re-implementation).
|
||||||
|
- Remove or replace the stale DESIGN §11 footer (*"Next step: AGENTIC-WP-0002"*) —
|
||||||
|
Phase 0–4 and retro are finished.
|
||||||
|
|
||||||
|
## Session-Close Env Export Convention
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-0011-T02
|
||||||
|
status: done
|
||||||
|
priority: high
|
||||||
|
state_hub_task_id: "1c20d5fa-83c2-46c7-b21e-be1ea241bae6"
|
||||||
|
```
|
||||||
|
|
||||||
|
Document the recommended environment export at session close for agents that run
|
||||||
|
**both** Helix Forge capture and kaizen `metrics record`. Cover at minimum:
|
||||||
|
|
||||||
|
| Variable | Source (Helix Forge) | Purpose |
|
||||||
|
|----------|----------------------|---------|
|
||||||
|
| `HELIX_SESSION_UID` | `Session.session_uid` after digest write | Primary correlation key |
|
||||||
|
| `HELIX_REPO` | session `repo` field | Project/repo scoping |
|
||||||
|
| `HELIX_FLAVOR` | session `flavor` | Agent runtime (claude/codex/grok) |
|
||||||
|
| `HELIX_TOKENS` | `digest.cost` totals | Token rollup for project metrics |
|
||||||
|
| `HELIX_INFRA_OVERHEAD_SHARE` | MCP/tool histogram share | Infra overhead attribution |
|
||||||
|
|
||||||
|
Place in DESIGN §11 (canonical spec) and a concise operator note in
|
||||||
|
`session_memory/README.md`. Align field names with kaizen-agentic ADR-004 mapping
|
||||||
|
table — do not invent parallel names.
|
||||||
|
|
||||||
|
## Stable Digest Read Path for `metrics correlate`
|
||||||
|
|
||||||
|
```task
|
||||||
|
id: AGENTIC-WP-0011-T03
|
||||||
|
status: done
|
||||||
|
priority: medium
|
||||||
|
state_hub_task_id: "80e35324-d46e-4737-88e7-0316088e7ace"
|
||||||
|
```
|
||||||
|
|
||||||
|
Give `kaizen-agentic metrics correlate <uid>` a stable, documented read convention
|
||||||
|
across hosts:
|
||||||
|
|
||||||
|
- Document `HELIX_STORE_DB` defaulting to `session_memory/.store/mem.db` (from
|
||||||
|
`config.toml` `[store].db_path`, absolute path when exported).
|
||||||
|
- Document the existing `Store.get_digest(session_uid)` JSON shape consumers may
|
||||||
|
rely on (outcome, cost, tool_histogram, repo, flavor, timestamps).
|
||||||
|
- Add a thin read entrypoint — e.g. `python -m session_memory.digest_lookup <uid>`
|
||||||
|
or `python -m session_memory.store --digest <uid> --json` — that prints one
|
||||||
|
digest without running a full ingest sweep. Unit-test the CLI; no new runtime
|
||||||
|
dependencies.
|
||||||
|
|
||||||
|
After workplan updates, notify the operator to run from `~/state-hub`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make fix-consistency REPO=agentic-resources
|
||||||
|
```
|
||||||
|
|
||||||
|
Mark the kaizen-agentic inbox message read once T01 is merged or the workplan is
|
||||||
|
filed (coordination ack).
|
||||||
Reference in New Issue
Block a user