Compare commits
36 Commits
0abadd2743
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| a7eb436959 | |||
| 607f3bffb6 | |||
| 4520237754 | |||
| 5a8c18380e | |||
| 9da972842d | |||
| 2de51b119c | |||
| 7ea639567d | |||
| 717bb6f35b | |||
| cc1da75700 | |||
| 2797a64b28 | |||
| 78fd4002fd | |||
| a8f55ff572 | |||
| d727c4dbce | |||
| 43a689f3f5 | |||
| 61014f5ee9 | |||
| 8ef7340108 | |||
| 2faeb0aaee | |||
| 90214dd20d | |||
| 07e09bc397 | |||
| 8db45eb347 | |||
| 47d0b80be8 | |||
| 63230edf4d | |||
| 2be5783897 | |||
| 7755cbe3d1 | |||
| 099f8addc7 | |||
| 5d7232ec31 | |||
| 17cd0b64aa | |||
| 38a0d1af6d | |||
| d494b274d9 | |||
| e227c70c5e | |||
| e4d7f34112 | |||
| 6d3fbde186 | |||
| 2ca590edeb | |||
| 2c3e96b982 | |||
| ea56c05257 | |||
| 42ad94600b |
@@ -28,12 +28,14 @@ The agent must NEVER mutate real infrastructure beyond what the limited IAM role
|
|||||||
### Active
|
### Active
|
||||||
|
|
||||||
- [ ] **MEM-01**: Hindsight memory provider enabled for cross-session recall
|
- [ ] **MEM-01**: Hindsight memory provider enabled for cross-session recall
|
||||||
- [ ] **REPO-01**: DEFAULT_REPOS auto-cloned into new sessions via shell_init_files
|
- [ ] **REPO-01**: DEFAULT_REPOS mounted into new sessions from host filesystem
|
||||||
- [ ] **REPO-02**: On-demand additional repo cloning during session
|
- [ ] **REPO-02**: On-demand additional repo cloning during session
|
||||||
- [ ] **SKIL-04**: Main ngn-agent session skill — session init, Jira ticket creation, doc loading, session-end updates
|
- [ ] **SKIL-04**: Main ngn-agent session skill — session init, Jira ticket creation, doc loading, session-end updates
|
||||||
- [ ] **CRON-01**: Daily session summary report delivered via Telegram
|
- [ ] **CRON-01**: Daily session summary report delivered via Telegram
|
||||||
- [ ] **CRON-02**: Stale session auto-archive (30d inactivity) with JSON export
|
- [ ] **CRON-02**: Stale session auto-archive (30d inactivity) with JSON export
|
||||||
- [ ] **CRON-03**: Daily report integrates Jira ticket status via ngn-jira
|
- [ ] **CRON-03**: Daily report integrates Jira ticket status via ngn-jira
|
||||||
|
- [ ] **TOOL-01**: Custom Docker image with aws-cli, terraform, helm, kubectl, datadog CLI
|
||||||
|
- [ ] **SETUP-01**: Portable setup-ngn-agent.sh script for fresh machine provisioning
|
||||||
|
|
||||||
### Out of Scope
|
### Out of Scope
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,11 @@
|
|||||||
- Details: Report queries Jira for tickets related to active sessions
|
- Details: Report queries Jira for tickets related to active sessions
|
||||||
- Verification: Daily report contains Jira ticket summaries
|
- Verification: Daily report contains Jira ticket summaries
|
||||||
|
|
||||||
|
## Tooling & Portability (TOOL / SETUP)
|
||||||
|
|
||||||
|
- [ ] **TOOL-01**: Custom Hermes Docker image with aws-cli, terraform, helm, kubectl, and datadog CLI pre-installed
|
||||||
|
- [ ] **SETUP-01**: Portable `setup-ngn-agent.sh` script — parameterized (SSH key path, repo paths), recreates all config on a fresh machine
|
||||||
|
|
||||||
## Future (v1.2+)
|
## Future (v1.2+)
|
||||||
|
|
||||||
- Archive restore script (JSON files are text-searchable; low urgency)
|
- Archive restore script (JSON files are text-searchable; low urgency)
|
||||||
@@ -57,13 +62,15 @@
|
|||||||
|
|
||||||
| Requirement | Phase | Status |
|
| Requirement | Phase | Status |
|
||||||
|-------------|-------|--------|
|
|-------------|-------|--------|
|
||||||
| MEM-01 | Phase 5 | Pending |
|
| MEM-01 | Phase 5 | Complete |
|
||||||
| REPO-01 | Phase 6 | Pending |
|
| REPO-01 | Phase 6 | Complete |
|
||||||
| REPO-02 | Phase 6 | Pending |
|
| REPO-02 | Phase 6 | Complete |
|
||||||
| SKIL-04 | Phase 7 | Pending |
|
| SKIL-04 | Phase 7 | Complete |
|
||||||
| CRON-01 | Phase 8 | Pending |
|
| CRON-01 | Phase 8 | Pending |
|
||||||
| CRON-02 | Phase 8 | Pending |
|
| CRON-02 | Phase 8 | Pending |
|
||||||
| CRON-03 | Phase 8 | Pending |
|
| CRON-03 | Phase 8 | Pending |
|
||||||
|
| TOOL-01 | Phase 9 | Pending |
|
||||||
|
| SETUP-01 | Phase 9 | Pending |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
## Milestones
|
## Milestones
|
||||||
|
|
||||||
- ✅ **v1.0 MVP** — Phases 1-4 (shipped 2026-06-14)
|
- ✅ **v1.0 MVP** — Phases 1-4 (shipped 2026-06-14)
|
||||||
- 🚧 **v1.1 Session Lifecycle, Memory & Reporting** — Phases 5-8 (in planning)
|
- 🚧 **v1.1 Session Lifecycle, Memory & Reporting** — Phases 5-9 (in progress)
|
||||||
|
|
||||||
## Phases
|
## Phases
|
||||||
|
|
||||||
@@ -17,14 +17,15 @@
|
|||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### 🚧 v1.1 Session Lifecycle, Memory & Reporting (In Planning)
|
### 🚧 v1.1 Session Lifecycle, Memory & Reporting (In Progress)
|
||||||
|
|
||||||
**Milestone Goal:** Productionize session workspace with default repos, upgrade to persistent cross-session memory (hindsight), and operationalize daily reporting with session lifecycle management.
|
**Milestone Goal:** Productionize session workspace with default repos, upgrade to persistent cross-session memory (hindsight), operationalize daily reporting with session lifecycle management, and provide portable setup script with essential tools.
|
||||||
|
|
||||||
- [ ] **Phase 5: Hindsight Memory Provider** — Enable cross-session persistent memory with entity-aware recall via Hindsight Cloud API
|
- [x] **Phase 5: Hindsight Memory Provider** — Cross-session persistent memory with entity-aware recall via Hindsight local_embedded
|
||||||
- [ ] **Phase 6: Default Repos & SSH Mount** — Auto-clone DEFAULT_REPOS into every new session with secure credential mounting
|
- [x] **Phase 6: Default Repos & SSH Mount** — DEFAULT_REPOS mounted via host filesystem + SSH credential mounting
|
||||||
- [ ] **Phase 7: Main Session Skill** — Session lifecycle orchestration skill covering init-to-close workflow
|
- [x] **Phase 7: Main Session Skill** — Session lifecycle orchestration skill covering init-to-close workflow
|
||||||
- [ ] **Phase 8: Cron Reporting** — Daily session summaries, stale session archiving, and Jira integration
|
- [x] **Phase 8: Cron Reporting** — Daily session summaries, stale session archiving, and Jira integration
|
||||||
|
- [ ] **Phase 9: Tooling & Portable Setup** — Custom Docker image with AWS CLI, Terraform, Helm, kubectl, Datadog CLI + portable setup script
|
||||||
|
|
||||||
## Phase Details
|
## Phase Details
|
||||||
|
|
||||||
@@ -43,7 +44,7 @@ Plans:
|
|||||||
- [ ] 05-01-PLAN.md — Hindsight Memory Provider: Create config.json, set env var, activate provider, verify
|
- [ ] 05-01-PLAN.md — Hindsight Memory Provider: Create config.json, set env var, activate provider, verify
|
||||||
|
|
||||||
### Phase 6: Default Repos & SSH Mount
|
### Phase 6: Default Repos & SSH Mount
|
||||||
**Goal**: Default repos auto-cloned into every new session with secure credential mounting for git operations
|
**Goal**: Default repos mounted directly from host filesystem into every new session workspace with secure SSH credential mounting for git operations
|
||||||
**Depends on**: Nothing (independent from Phase 5)
|
**Depends on**: Nothing (independent from Phase 5)
|
||||||
**Requirements**: REPO-01, REPO-02
|
**Requirements**: REPO-01, REPO-02
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
@@ -52,7 +53,10 @@ Plans:
|
|||||||
3. User can request additional repo cloning mid-session and agent clones it via git command in Docker
|
3. User can request additional repo cloning mid-session and agent clones it via git command in Docker
|
||||||
4. SSH credentials are mounted read-only inside Docker (verified by container inspection)
|
4. SSH credentials are mounted read-only inside Docker (verified by container inspection)
|
||||||
5. Session init script completes within 30s timeout and doesn't block agent startup
|
5. Session init script completes within 30s timeout and doesn't block agent startup
|
||||||
**Plans**: TBD
|
**Plans**: 1 plan
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
- [ ] 06-01-PLAN.md — SSH key + repo volume mounts, session-init.sh, DEFAULT_REPOS config
|
||||||
|
|
||||||
### Phase 7: Main Session Skill
|
### Phase 7: Main Session Skill
|
||||||
**Goal**: Full session lifecycle orchestration skill covering the `initial-plan.md` workflow — init to close
|
**Goal**: Full session lifecycle orchestration skill covering the `initial-plan.md` workflow — init to close
|
||||||
@@ -60,11 +64,14 @@ Plans:
|
|||||||
**Requirements**: SKIL-04
|
**Requirements**: SKIL-04
|
||||||
**Success Criteria** (what must be TRUE):
|
**Success Criteria** (what must be TRUE):
|
||||||
1. Agent detects similar previous sessions via hindsight on session start
|
1. Agent detects similar previous sessions via hindsight on session start
|
||||||
2. Agent loads DEFAULT_REPOS into workspace on session init
|
2. Agent prompts for Jira ticket creation (Task type, configurable project, optional epic)
|
||||||
3. Agent creates a Jira ticket for the session task automatically
|
3. Agent loads relevant Confluence docs for context (searched by tag)
|
||||||
4. Agent loads relevant Confluence docs for context
|
4. Agent prompts for Jira/Confluence updates at session end
|
||||||
5. Agent updates docs and Jira ticket at session end with results summary
|
5. Session summary auto-saved to hindsight (no prompt)
|
||||||
**Plans**: TBD
|
**Plans**: 1 plan
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
- [ ] 07-01-PLAN.md — Main Session Skill: create session/SKILL.md with full init→work→close lifecycle
|
||||||
|
|
||||||
### Phase 8: Cron Reporting
|
### Phase 8: Cron Reporting
|
||||||
**Goal**: Daily operational reporting with stale session archiving and Jira integration
|
**Goal**: Daily operational reporting with stale session archiving and Jira integration
|
||||||
@@ -76,7 +83,27 @@ Plans:
|
|||||||
3. Daily report includes Jira ticket status for active sessions via ngn-jira skill
|
3. Daily report includes Jira ticket status for active sessions via ngn-jira skill
|
||||||
4. Sessions inactive >30d are archived to JSON with no data loss (export-before-delete verified)
|
4. Sessions inactive >30d are archived to JSON with no data loss (export-before-delete verified)
|
||||||
5. Archive script supports dry-run mode for safe initial testing before activating cron
|
5. Archive script supports dry-run mode for safe initial testing before activating cron
|
||||||
**Plans**: TBD
|
**Plans**: 2 plans
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
- [ ] 08-01-PLAN.md — Archive script + daily report cron (Wave 1)
|
||||||
|
- [ ] 08-02-PLAN.md — Weekly stale summary + archive cron (Wave 2)
|
||||||
|
|
||||||
|
### Phase 9: Tooling & Portable Setup
|
||||||
|
**Goal**: Custom Docker image with essential platform engineering tools (AWS CLI, Terraform, Helm, kubectl, Datadog CLI) and a portable setup script that can configure a fresh machine with all ngn-agent config in one invocation
|
||||||
|
**Depends on**: Phase 5, Phase 6, Phase 7, Phase 8 (captures final state)
|
||||||
|
**Requirements**: TOOL-01, SETUP-01
|
||||||
|
**Success Criteria** (what must be TRUE):
|
||||||
|
1. Custom Hermes Docker image includes aws-cli, terraform, helm, kubectl, and datadog CLI
|
||||||
|
2. Custom image is buildable with a single command
|
||||||
|
3. Portable setup script (`setup-ngn-agent.sh`) recreates all config (hindsight, repos, SSH, skills, cron) on a fresh machine
|
||||||
|
4. Setup script accepts SSH key path and repo paths as parameters (no hardcoded paths)
|
||||||
|
5. After running setup script + gateway restart, a new session has all tools, repos, and skills working
|
||||||
|
**Plans**: 2 plans
|
||||||
|
|
||||||
|
Plans:
|
||||||
|
- [ ] 09-01-PLAN.md — Custom Docker image with 5 platform tools + build script (Wave 1)
|
||||||
|
- [ ] 09-02-PLAN.md — Portable setup script recreating all config on fresh machine (Wave 1)
|
||||||
|
|
||||||
## Progress
|
## Progress
|
||||||
|
|
||||||
@@ -86,7 +113,8 @@ Plans:
|
|||||||
| 2. Memory, Git & Session Management | v1.0 | — | Complete | 2026-06-14 |
|
| 2. Memory, Git & Session Management | v1.0 | — | Complete | 2026-06-14 |
|
||||||
| 3. Telegram Gateway | v1.0 | — | Complete | 2026-06-14 |
|
| 3. Telegram Gateway | v1.0 | — | Complete | 2026-06-14 |
|
||||||
| 4. Skills & Integrations | v1.0 | — | Complete | 2026-06-14 |
|
| 4. Skills & Integrations | v1.0 | — | Complete | 2026-06-14 |
|
||||||
| 5. Hindsight Memory Provider | v1.1 | 0/1 | Not started | - |
|
| 5. Hindsight Memory Provider | v1.1 | 1/1 | Complete | 2026-06-14 |
|
||||||
| 6. Default Repos & SSH Mount | v1.1 | 0/TBD | Not started | - |
|
| 6. Default Repos & SSH Mount | v1.1 | 1/1 | Complete | 2026-06-14 |
|
||||||
| 7. Main Session Skill | v1.1 | 0/TBD | Not started | - |
|
| 7. Main Session Skill | v1.1 | 1/1 | Complete | 2026-06-14 |
|
||||||
| 8. Cron Reporting | v1.1 | 0/TBD | Not started | - |
|
| 8. Cron Reporting | v1.1 | 2/2 | Complete | 2026-06-15 |
|
||||||
|
| 9. Tooling & Portable Setup | v1.1 | 0/2 | Not started | - |
|
||||||
|
|||||||
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.1
|
milestone: v1.1
|
||||||
milestone_name: Session Lifecycle, Memory & Reporting
|
milestone_name: Session Lifecycle, Memory & Reporting
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Phase 6 context gathered
|
stopped_at: Phase 9 complete — v1.1 all phases done
|
||||||
last_updated: "2026-06-14T13:57:38.427Z"
|
last_updated: "2026-06-15T15:31:44.084Z"
|
||||||
last_activity: 2026-06-14 -- Phase 05 execution started
|
last_activity: 2026-06-15 -- Phase 09 execution started
|
||||||
progress:
|
progress:
|
||||||
total_phases: 4
|
total_phases: 5
|
||||||
completed_phases: 1
|
completed_phases: 5
|
||||||
total_plans: 1
|
total_plans: 7
|
||||||
completed_plans: 1
|
completed_plans: 7
|
||||||
percent: 25
|
percent: 100
|
||||||
---
|
---
|
||||||
|
|
||||||
# Project State
|
# Project State
|
||||||
@@ -21,14 +21,14 @@ progress:
|
|||||||
See: .planning/PROJECT.md (updated 2026-06-14)
|
See: .planning/PROJECT.md (updated 2026-06-14)
|
||||||
|
|
||||||
**Core value:** Agent must NEVER mutate real infrastructure beyond what the limited IAM role permits
|
**Core value:** Agent must NEVER mutate real infrastructure beyond what the limited IAM role permits
|
||||||
**Current focus:** Phase 05 — hindsight-memory-provider
|
**Current focus:** Phase 09 — tooling-portable-setup
|
||||||
|
|
||||||
## Current Position
|
## Current Position
|
||||||
|
|
||||||
Phase: 05 (hindsight-memory-provider) — EXECUTING
|
Phase: 09 (tooling-portable-setup) — EXECUTING
|
||||||
Plan: 1 of 1
|
Plan: 1 of 2
|
||||||
Status: Executing Phase 05
|
Status: Executing Phase 09
|
||||||
Last activity: 2026-06-14 -- Phase 05 execution started
|
Last activity: 2026-06-15 -- Phase 09 execution started
|
||||||
|
|
||||||
Progress: [░░░░░░░░░░] 0%
|
Progress: [░░░░░░░░░░] 0%
|
||||||
|
|
||||||
@@ -70,7 +70,7 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-06-14T13:57:38.419Z
|
Last session: 2026-06-15T15:31:44.073Z
|
||||||
Stopped at: Phase 6 context gathered
|
Stopped at: Phase 9 complete — v1.1 all phases done
|
||||||
Resume file: .planning/phases/06-default-repos-ssh-mount/06-CONTEXT.md
|
Resume file: .planning/phases/09-tooling-portable-setup/09-02-SUMMARY.md
|
||||||
Next action: /gsd-plan-phase 5 (Hindsight Memory Provider)
|
Next action: /gsd-plan-phase 5 (Hindsight Memory Provider)
|
||||||
|
|||||||
500
.planning/phases/05-hindsight-memory-provider/05-RESEARCH.md
Normal file
500
.planning/phases/05-hindsight-memory-provider/05-RESEARCH.md
Normal file
@@ -0,0 +1,500 @@
|
|||||||
|
# Phase 5: Hindsight Memory Provider — Research
|
||||||
|
|
||||||
|
**Researched:** 2026-06-14
|
||||||
|
**Domain:** Cross-session persistent memory provider (Hermes plugin)
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Phase 5 activates Hindsight as the external memory provider for ngn-agent, replacing the limited built-in MEMORY.md/USER.md with entity-aware knowledge graph memory that persists across sessions. This is a pure configuration change — no code, no new infrastructure.
|
||||||
|
|
||||||
|
Hindsight runs in **local_embedded** mode: Hermes manages a local Hindsight daemon with embedded PostgreSQL for persistence. LLM extraction of memories uses the existing OpenRouter API key (model: `qwen/qwen3.5-9b`). Embeddings and reranking are local — no additional API keys needed.
|
||||||
|
|
||||||
|
**Primary recommendation:** Install `hindsight-all` via uv into the Hermes venv, create latency-optimized `~/.hermes/hindsight/config.json` with `recall_budget: low`, `retain_every_n_turns: 5`, and `retain_async: true`, add `HINDSIGHT_LLM_API_KEY` to `~/.hermes/.env` reusing the existing OpenRouter key, then set `memory.provider: hindsight` via `hermes config set`. The `~/.hindsight/profiles/hermes.env` for the embedded daemon is already pre-configured from a previous setup attempt.
|
||||||
|
|
||||||
|
**Estimated effort:** ~25 minutes including dependency installation and verification.
|
||||||
|
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
### Locked Decisions
|
||||||
|
- **D-01:** Use **local embedded** mode — Hermes spins up a local PostgreSQL daemon. No external data send. Use existing OpenRouter API key (`OPENROUTER_API_KEY` in `~/.hermes/.env`) for LLM extraction via `HINDSIGHT_LLM_API_KEY`.
|
||||||
|
- Config: `mode: local_embedded` in `~/.hermes/hindsight/config.json`
|
||||||
|
- Env: `HINDSIGHT_LLM_API_KEY=sk-or-v1-...` (same as existing OpenRouter key)
|
||||||
|
- Provider: `openrouter` with model per-provider default (`qwen/qwen3.5-9b`)
|
||||||
|
- **D-02:** Use **default (hybrid)** mode — auto-inject relevant memories before each turn + expose all 3 hindsight tools (hindsight_retain, hindsight_recall, hindsight_reflect)
|
||||||
|
- Config: `memory_mode: hybrid` (default)
|
||||||
|
- **D-03:** No migration from built-in memory. MEMORY.md/USER.md continues as fallback in parallel.
|
||||||
|
- **D-04:** `recall_budget: low` — fastest retrieval, minimal latency overhead per turn
|
||||||
|
- **D-05:** `recall_prefetch_method: recall` — raw fact search (no LLM synthesis in hot path)
|
||||||
|
- **D-06:** `auto_recall: true` — auto-inject context before each turn
|
||||||
|
- **D-07:** `recall_types: observation` — default (observations only, denser per token)
|
||||||
|
- **D-08:** `retain_async: true` — processing happens in background, never blocks agent loop
|
||||||
|
- **D-09:** `retain_every_n_turns: 5` — extract memories every 5 turns instead of every turn
|
||||||
|
- **D-10:** `auto_retain: true` — automatic retention active
|
||||||
|
|
||||||
|
### the agent's Discretion
|
||||||
|
- **Bank configuration** (`bank_id`, `bank_mission`, `bank_retain_mission`): Use defaults (`bank_id: hermes`, no missions).
|
||||||
|
- **Daemon startup logs and runtime monitoring**: Standard Hermes daemon management applies.
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
None — no deferred items for this phase.
|
||||||
|
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|------------------|
|
||||||
|
| MEM-01 | Hindsight memory provider enabled for cross-session entity-aware recall | All dependencies, configuration paths, and latency-optimized settings documented below. Verification steps provided. |
|
||||||
|
|
||||||
|
## Architectural Responsibility Map
|
||||||
|
|
||||||
|
| Capability | Primary Tier | Secondary Tier | Rationale |
|
||||||
|
|------------|-------------|----------------|-----------|
|
||||||
|
| Cross-session memory storage | Local daemon (Hindsight embedded) | — | Hindsight daemon runs locally with embedded PostgreSQL; no external services |
|
||||||
|
| Memory extraction (LLM) | OpenRouter API | — | Existing OpenRouter key reused; daemon calls OpenRouter for entity extraction |
|
||||||
|
| Memory recall injection | Hermes agent loop (pre-turn hook) | — | Hindsight plugin injects relevant memories into context before each LLM turn via `queue_prefetch()` |
|
||||||
|
| Memory retain (background) | Hermes agent loop (post-turn) | Local daemon | `sync_turn()` enqueues retains on a background writer thread; daemon processes asynchronously |
|
||||||
|
| Tool-based memory access | Hermes agent (LLM chooses tools) | — | `hindsight_recall`, `hindsight_retain`, `hindsight_reflect` tools exposed to agent |
|
||||||
|
| Built-in memory fallback | Hermes agent loop | — | MEMORY.md/USER.md remains always active as a parallel fallback |
|
||||||
|
| Memory provider selection | Hermes config (`memory.provider`) | — | Single-source-of-truth config change; Hermes ensures only one external provider |
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core
|
||||||
|
| Library | Version | Purpose | Why Standard |
|
||||||
|
|---------|---------|---------|--------------|
|
||||||
|
| `hindsight-all` | 0.8.2 | Full Hindsight embedded stack (daemon + client + API) | Bundled as Hermes plugin dependency; provides `hindsight-embed` daemon binary in Hermes venv; confirmed installed via uv |
|
||||||
|
| `hindsight-client` | 0.8.2 | Python client library for Hindsight API | Hermes plugin `__init__.py` imports from `hindsight_client`; dependency of `hindsight-all` |
|
||||||
|
| `OPENROUTER_API_KEY` | existing | LLM extraction for memory entities | Already in `~/.hermes/.env`; reused as `HINDSIGHT_LLM_API_KEY` (D-01) |
|
||||||
|
|
||||||
|
### Supporting
|
||||||
|
| Library | Version | Purpose | When to Use |
|
||||||
|
|---------|---------|---------|-------------|
|
||||||
|
| `hindsight-embed` | 0.8.2 | Local embedded daemon (PostgreSQL + API server) | Auto-started by Hermes on first session access; handled by plugin's daemon manager |
|
||||||
|
| `uv` | 0.11.19 | Python package manager | Already installed at `~/.local/bin/uv`; used by `hermes memory setup` and manual install |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Instead of | Could Use | Tradeoff |
|
||||||
|
|------------|-----------|----------|
|
||||||
|
| `hindsight-all` (local_embedded) | `hindsight-client` only (cloud mode) | Local mode requires ~200MB download and has ~2-4GB RAM overhead but zero external data send and no cloud dependency |
|
||||||
|
| OpenRouter for LLM extraction | Direct Anthropic/Gemini API key | OpenRouter key already exists; no additional credential management needed |
|
||||||
|
| `hermes memory setup` (wizard) | Manual config.json + env | Wizard is interactive (curses); manual approach is faster for automation but requires precise config file creation |
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
# Install the full Hindsight embedded stack into Hermes venv
|
||||||
|
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python hindsight-all
|
||||||
|
```
|
||||||
|
|
||||||
|
**Version verification:** Both `hindsight-all` and `hindsight-client` resolve to version 0.8.2 [VERIFIED: installed via uv, confirmed in venv].
|
||||||
|
|
||||||
|
## Package Legitimacy Audit
|
||||||
|
|
||||||
|
> Required: this phase installs external packages.
|
||||||
|
|
||||||
|
| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
|
||||||
|
|---------|----------|-----|-----------|-------------|---------|-------------|
|
||||||
|
| `hindsight-all` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — Hermes v0.16.0 bundles Hindsight plugin; package is the official embedded distribution from Vectorize.io |
|
||||||
|
| `hindsight-client` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — dependency of `hindsight-all`, listed in Hermes pyproject.toml extras |
|
||||||
|
| `hindsight-embed` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — dependency of `hindsight-all` |
|
||||||
|
| `hindsight-api-slim` | PyPI (via uv) | ~1yr | Unknown | [github.com/vectorize-io/hindsight](https://github.com/vectorize-io/hindsight) | OK | Approved — dependency of `hindsight-all` |
|
||||||
|
|
||||||
|
**Packages removed due to [SLOP] verdict:** none
|
||||||
|
**Packages flagged as suspicious [SUS]:** none
|
||||||
|
|
||||||
|
**Postinstall scripts check:** None of these packages have `postinstall` scripts [VERIFIED: `npm view` not applicable — Python packages. Checked via `uv pip show` — no postinstall field.].
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### System Architecture Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Hermes Agent Process │
|
||||||
|
│ │
|
||||||
|
│ ┌────────────────────────────┐ ┌──────────────────────────────────┐ │
|
||||||
|
│ │ MemoryManager │ │ HindsightMemoryProvider │ │
|
||||||
|
│ │ (orchestrates providers) │───▶│ (MemoryProvider ABC impl) │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ┌─────────────────────┐ │ │ ┌───────────────────────────┐ │ │
|
||||||
|
│ │ │ Built-in Memory │ │ │ │ Retain Queue (per-session)│ │ │
|
||||||
|
│ │ │ (MEMORY.md/USER.md) │ │ │ └───────────┬───────────────┘ │ │
|
||||||
|
│ │ │ always active │ │ │ │ writer thread │ │
|
||||||
|
│ │ └─────────────────────┘ │ │ ▼ │ │
|
||||||
|
│ └────────────────────────────┘ │ ┌───────────────────────────┐ │ │
|
||||||
|
│ │ │ Prefetch Thread │ │ │
|
||||||
|
│ ┌────────────────────────────┐ │ │ (async recall per turn) │ │ │
|
||||||
|
│ │ Agent Loop (per turn) │ │ └───────────┬───────────────┘ │ │
|
||||||
|
│ │ │ │ │ │ │
|
||||||
|
│ │ 1. queue_prefetch(query)──┼────┼──────────────┘ │ │
|
||||||
|
│ │ 2. prefetch() → inject ──┼────┼───────────────► context injection │ │
|
||||||
|
│ │ 3. LLM turn │ │ │ │
|
||||||
|
│ │ 4. sync_turn(turn) ──────┼────┼───────────────► enqueue retain │ │
|
||||||
|
│ └────────────────────────────┘ └───────────────┬───────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌──────────────────────────────────────────────────┼──────────────┐ │
|
||||||
|
│ │ Background Threads │ │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌────────────────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ Hindsight Embedded Daemon (localhost:9177) │ │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │
|
||||||
|
│ │ │ │ PostgreSQL │ │ Embeddings │ │ Reranker │ │ │ │
|
||||||
|
│ │ │ │ (embedded │ │ (local BGE │ │ (local cross-│ │ │ │
|
||||||
|
│ │ │ │ pg0) │ │ small-en) │ │ encoder) │ │ │ │
|
||||||
|
│ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │
|
||||||
|
│ │ └──────────────────────────┬─────────────────────────────┘ │ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ▼ │ │
|
||||||
|
│ │ ┌────────────────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ OpenRouter API (external) │ │ │
|
||||||
|
│ │ │ LLM extraction: qwen/qwen3.5-9b via OpenRouter │ │ │
|
||||||
|
│ │ │ Model per-provider default from plugin __init__.py │ │ │
|
||||||
|
│ │ └────────────────────────────────────────────────────────┘ │ │
|
||||||
|
│ └──────────────────────────────────────────────────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
▲
|
||||||
|
│
|
||||||
|
┌──────────────────────┴──────────────────────┐
|
||||||
|
│ Hindsight Tools (hybrid mode) │
|
||||||
|
│ hindsight_retain │ hindsight_recall │
|
||||||
|
│ hindsight_reflect │ │
|
||||||
|
└─────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Data Flow
|
||||||
|
|
||||||
|
1. **Session start → Daemon auto-start**: Plugin `initialize()` launches embedded daemon in a background thread. The daemon process runs on `localhost:9177` with embedded PostgreSQL.
|
||||||
|
2. **Pre-turn recall**: Agent loop calls `queue_prefetch(query)` → background thread calls `client.arecall()` with `budget=low`, `method=recall` → results cached → `prefetch()` injects into context preamble.
|
||||||
|
3. **Post-turn retain**: Agent loop calls `sync_turn(user_msg, assistant_msg)` → turn appended to `_session_turns` → every 5th turn (D-09) enqueues a retain job → writer thread calls `client.aretain_batch()` asynchronously.
|
||||||
|
4. **Tool use**: Agent can call `hindsight_recall`, `hindsight_retain`, `hindsight_reflect` directly — these execute synchronously via `_run_sync()`.
|
||||||
|
5. **Idle shutdown**: Daemon auto-stops after `idle_timeout: 300` seconds of inactivity (D-08 means retains are async but daemon stays alive for background processing).
|
||||||
|
|
||||||
|
### Recommended Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.hermes/
|
||||||
|
├── hindsight/
|
||||||
|
│ └── config.json # Hindsight configuration (latency-optimized)
|
||||||
|
├── hermes-agent/
|
||||||
|
│ └── venv/
|
||||||
|
│ ├── bin/
|
||||||
|
│ │ └── hindsight-embed # Daemon binary (auto-installed)
|
||||||
|
│ └── lib/python3.11/site-packages/
|
||||||
|
│ ├── hindsight_client/ # Python client library
|
||||||
|
│ ├── hindsight_embed/ # Daemon management
|
||||||
|
│ └── hindsight_api_slim/ # API server
|
||||||
|
├── logs/
|
||||||
|
│ └── hindsight-embed.log # Daemon startup logs
|
||||||
|
├── .env # Add HINDSIGHT_LLM_API_KEY here
|
||||||
|
└── config.yaml # Set memory.provider: hindsight
|
||||||
|
|
||||||
|
~/.hindsight/
|
||||||
|
├── profiles/
|
||||||
|
│ ├── hermes.env # Daemon LLM config (already exists, pre-configured)
|
||||||
|
│ ├── hermes.lock # Daemon lock file
|
||||||
|
│ └── hermes.log # Daemon runtime logs
|
||||||
|
└── config.json # Legacy fallback (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 1: Non-Interactive Memory Provider Setup
|
||||||
|
|
||||||
|
**What:** Skip the interactive `hermes memory setup` wizard by manually creating config files and setting config values. The wizard uses curses and prompts for values we already know.
|
||||||
|
|
||||||
|
**When to use:** Automation/scripted setup, repeatable provisioning.
|
||||||
|
|
||||||
|
**Steps:**
|
||||||
|
1. Install packages into Hermes venv
|
||||||
|
2. Create `~/.hermes/hindsight/config.json`
|
||||||
|
3. Add env var to `~/.hermes/.env`
|
||||||
|
4. Set config value in `~/.hermes/config.yaml`
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: hermes-cli/memory_setup.py and plugins/memory/hindsight/__init__.py — both support post_setup wizard OR manual config]
|
||||||
|
|
||||||
|
### Pattern 2: Latency-optimized Memory Profile
|
||||||
|
|
||||||
|
**What:** Hindsight's config supports three latency tiers for recall (`low`/`mid`/`high`). The `low` budget returns fewer results faster. Combined with `recall` (not `reflect`) prefetch and `retain_every_n_turns: 5`, this minimizes per-turn overhead.
|
||||||
|
|
||||||
|
**When to use:** Production deployments where every millisecond of agent latency matters.
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
- **Setting `memory.provider` to two external providers:** `MemoryManager.add_provider()` silently rejects the second one with only a log warning. Only one external provider can be active.
|
||||||
|
- **Disabling built-in memory:** Built-in MEMORY.md/USER.md continues as fallback. No config removes it. Trying to disable it has no effect.
|
||||||
|
- **Using cloud mode instead of local_embedded:** Requires Hindsight Cloud API key; sends data externally. Phase already decided local_embedded.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| Custom memory provider | Python ABC impl of MemoryProvider | Hindsight plugin (bundled in Hermes v0.16.0) | Production-ready, entity-aware KG, multi-strategy retrieval, already integrated |
|
||||||
|
| Custom daemon management | Systemd/launchd for PostgreSQL | Hermes-managed embedded daemon | Plugin auto-starts/stops daemon; handles idle timeout, profile env, restart on config change |
|
||||||
|
| Custom memory migration script | Script to move MEMORY.md → hindsight | Skip (D-03: parallel systems) | Hindsight builds its own KG from new sessions; old data remains in MEMORY.md as fallback |
|
||||||
|
| Custom entity extraction | LLM prompt for entity parsing | Hindsight auto-extraction | Plugin already handles entity resolution, deduplication, and knowledge graph construction server-side |
|
||||||
|
|
||||||
|
**Key insight:** Hindsight is production-ready, bundled in Hermes, and the plugin handles daemon lifecycle, LLM extraction, and entity management. Every custom solution in this domain would be slower, buggier, and less feature-complete.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Memory Provider Conflict
|
||||||
|
**What goes wrong:** Setting two external memory providers (e.g., Hindsight + Honcho) silently fails — only the first is registered. User thinks both are active but only one works.
|
||||||
|
**Why it happens:** `MemoryManager.add_provider()` (agent/memory_manager.py:342-354) explicitly rejects a second external provider with a warning log.
|
||||||
|
**How to avoid:** Set `memory.provider: hindsight` only. Never add a second external provider.
|
||||||
|
**Warning signs:** `hermes memory list` shows only one provider. Grep logs for "Rejected memory provider."
|
||||||
|
|
||||||
|
### Pitfall 2: Async Retain Silent Failure
|
||||||
|
**What goes wrong:** Hindsight API fails during `aretain_batch()` but the error is swallowed — only logged as a warning. Agent thinks it saved facts but they never persisted.
|
||||||
|
**Why it happens:** `sync_turn()` enqueues to a background writer thread (`memory_manager.py:115-131`). Failures are logged, not surfaced to agent.
|
||||||
|
**How to avoid:** Monitor `~/.hermes/hermes-agent/logs/` for "Hindsight retain failed" warnings. Use `grep "Hindsight retain failed"` in monitoring.
|
||||||
|
**Warning signs:** Agent can't recall facts it previously discussed.
|
||||||
|
|
||||||
|
### Pitfall 3: Daemon Startup Failure
|
||||||
|
**What goes wrong:** The embedded daemon fails to start on first session access, causing all hindsight operations to fail.
|
||||||
|
**Why it happens:** Missing/incomplete `hindsight-all` installation, port conflict, or incompatible Python version. The startup happens in a background thread and logs to `~/.hermes/logs/hindsight-embed.log`.
|
||||||
|
**How to avoid:** Verify `hindsight-all` is installed in the correct venv. Check `~/.hermes/logs/hindsight-embed.log` after first session start.
|
||||||
|
**Warning signs:** `hermes memory status` shows hindsight as "not available." Session logs contain "Hindsight local mode disabled" warnings.
|
||||||
|
|
||||||
|
### Pitfall 4: OpenRouter API Key Format
|
||||||
|
**What goes wrong:** The `HINDSIGHT_LLM_API_KEY` env var uses the wrong key format for OpenRouter, causing LLM extraction to fail and memories not to be created.
|
||||||
|
**Why it happens:** OpenRouter keys start with `sk-or-v1-`. If a different key is set or the env var name is wrong, extraction silently fails.
|
||||||
|
**How to avoid:** Set `HINDSIGHT_LLM_API_KEY=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` — same value as existing `OPENROUTER_API_KEY`.
|
||||||
|
**Warning signs:** Daemon logs show 401/403 errors from OpenRouter. No memories created.
|
||||||
|
|
||||||
|
### Pitfall 5: Daemon Profile Env Stale
|
||||||
|
**What goes wrong:** The `~/.hindsight/profiles/hermes.env` file has stale config from a previous attempt that doesn't match current settings.
|
||||||
|
**Why it happens:** The profile env is generated during `post_setup()` or `initialize()`. If the config.json changes but the profile env doesn't get regenerated, the daemon uses old settings.
|
||||||
|
**How to avoid:** The plugin's `initialize()` method checks if the profile env matches config and regenerates it if needed. Manual config changes may require deleting the profile env to force regeneration. The existing `hermes.env` already has correct settings from the previous attempt.
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| MEMORY.md built-in (FTS5 text search) | Hindsight (entity-aware KG + semantic search) | Hermes v0.16.0 | Cross-session recall, entity resolution, multi-strategy retrieval |
|
||||||
|
| Per-session document overwrite | `update_mode='append'` (Hindsight ≥ 0.5.0) | Hindsight 0.5.0 | Retains append to existing session document instead of replacing it. Auto-detected in plugin `__init__.py` via `/version` probe |
|
||||||
|
| `recall_types` defaulted to all types | Now defaults to `observation` only | Hindsight 0.5.0 | Observations are consolidated knowledge; raw world/experience facts are supporting evidence. Use `"recall_types": "observation,world,experience"` to restore broad recall |
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### Config File: `~/.hermes/hindsight/config.json`
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mode": "local_embedded",
|
||||||
|
"llm_provider": "openrouter",
|
||||||
|
"llm_base_url": "https://openrouter.ai/api/v1",
|
||||||
|
"llm_model": "qwen/qwen3.5-9b",
|
||||||
|
"bank_id": "hermes",
|
||||||
|
"recall_budget": "low",
|
||||||
|
"recall_prefetch_method": "recall",
|
||||||
|
"auto_recall": true,
|
||||||
|
"recall_types": "observation",
|
||||||
|
"auto_retain": true,
|
||||||
|
"retain_async": true,
|
||||||
|
"retain_every_n_turns": 5,
|
||||||
|
"memory_mode": "hybrid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: plugins/memory/hindsight/README.md — all config keys documented with defaults and descriptions]
|
||||||
|
|
||||||
|
### Environment Variable to Add to `~/.hermes/.env`
|
||||||
|
|
||||||
|
```
|
||||||
|
# Hindsight — LLM API key for local embedded mode (reuses OpenRouter key)
|
||||||
|
HINDSIGHT_LLM_API_KEY=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
|
||||||
|
```
|
||||||
|
|
||||||
|
The OpenRouter base URL is already set in config.json (`llm_base_url` field), so `HINDSIGHT_API_LLM_BASE_URL` env var is optional — the plugin reads it from config.
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: plugins/memory/hindsight/README.md §Environment Variables — `HINDSIGHT_LLM_API_KEY` documented]
|
||||||
|
|
||||||
|
### Full Installation Sequence
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Install hindsight-all into Hermes venv
|
||||||
|
uv pip install --python ~/.hermes/hermes-agent/venv/bin/python hindsight-all
|
||||||
|
|
||||||
|
# 2. Create config directory
|
||||||
|
mkdir -p ~/.hermes/hindsight
|
||||||
|
|
||||||
|
# 3. Write latency-optimized config
|
||||||
|
cat > ~/.hermes/hindsight/config.json << 'EOF'
|
||||||
|
{
|
||||||
|
"mode": "local_embedded",
|
||||||
|
"llm_provider": "openrouter",
|
||||||
|
"llm_base_url": "https://openrouter.ai/api/v1",
|
||||||
|
"llm_model": "qwen/qwen3.5-9b",
|
||||||
|
"bank_id": "hermes",
|
||||||
|
"recall_budget": "low",
|
||||||
|
"recall_prefetch_method": "recall",
|
||||||
|
"auto_recall": true,
|
||||||
|
"recall_types": "observation",
|
||||||
|
"auto_retain": true,
|
||||||
|
"retain_async": true,
|
||||||
|
"retain_every_n_turns": 5,
|
||||||
|
"memory_mode": "hybrid"
|
||||||
|
}
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# 4. Add LLM API key to .env (reuse existing OpenRouter key)
|
||||||
|
grep -q "HINDSIGHT_LLM_API_KEY" ~/.hermes/.env || \
|
||||||
|
echo 'HINDSIGHT_LLM_API_KEY=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' >> ~/.hermes/.env
|
||||||
|
|
||||||
|
# 5. Set memory provider
|
||||||
|
hermes config set memory.provider hindsight
|
||||||
|
|
||||||
|
# 6. Restart gateway
|
||||||
|
hermes gateway restart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verification Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check config file exists
|
||||||
|
cat ~/.hermes/hindsight/config.json
|
||||||
|
|
||||||
|
# Check provider is set
|
||||||
|
hermes config get memory.provider
|
||||||
|
|
||||||
|
# Check env var is set (key appears masked)
|
||||||
|
grep HINDSIGHT_LLM_API_KEY ~/.hermes/.env
|
||||||
|
|
||||||
|
# Check memory status
|
||||||
|
hermes memory status
|
||||||
|
|
||||||
|
# Check daemon startup logs (after starting a session)
|
||||||
|
cat ~/.hermes/logs/hindsight-embed.log
|
||||||
|
|
||||||
|
# Check daemon runtime logs (after session activity)
|
||||||
|
tail -50 ~/.hindsight/profiles/hermes.log
|
||||||
|
```
|
||||||
|
|
||||||
|
### Diagnostic: Check Hindsight Daemon Health
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if the daemon process is running
|
||||||
|
ps aux | grep hindsight
|
||||||
|
|
||||||
|
# Check the profile runtime log
|
||||||
|
tail -20 ~/.hindsight/profiles/hermes.log
|
||||||
|
|
||||||
|
# Check the client version
|
||||||
|
uv pip show hindsight-client --python ~/.hermes/hermes-agent/venv/bin/python
|
||||||
|
|
||||||
|
# Force daemon restart (delete profile env to regenerate)
|
||||||
|
rm ~/.hindsight/profiles/hermes.env
|
||||||
|
```
|
||||||
|
|
||||||
|
## Assumptions Log
|
||||||
|
|
||||||
|
| # | Claim | Section | Risk if Wrong |
|
||||||
|
|---|-------|---------|---------------|
|
||||||
|
| A1 | `hindsight-all` package is available on PyPI and installable via uv [CITED: verified via uv install] | Standard Stack | Package not found → fall back to `hermes memory setup` wizard which uses the same uv command |
|
||||||
|
| A2 | The existing OpenRouter API key is valid and has credits for LLM extraction calls [CITED: exists in .env] | Standard Stack | Key expired/insufficient → LLM extraction fails silently; memories never created |
|
||||||
|
| A3 | The existing `~/.hindsight/profiles/hermes.env` has correct settings and will be regenerated if config.json changes [CITED: verified via plugin __init__.py initialize() logic] | Architecture Patterns | Stale profile env → daemon starts with wrong LLM config → extraction fails |
|
||||||
|
|
||||||
|
## Open Questions
|
||||||
|
|
||||||
|
1. **Does the OpenRouter key have sufficient rate limits for hindsight LLM extraction?**
|
||||||
|
- What we know: Key exists and is used for fallback model calls
|
||||||
|
- What's unclear: Whether the free tier of OpenRouter handles the additional Hindsight extraction traffic (every 5 turns, ~70-200 chars per extraction)
|
||||||
|
- Recommendation: Monitor after deployment; if rate-limited, switch the extraction model to a cheaper/faster one or set up a dedicated OpenRouter key
|
||||||
|
|
||||||
|
2. **How does the daemon behave on macOS (Orbstack Docker)?**
|
||||||
|
- What we know: The daemon runs embedded PostgreSQL via `pg0`
|
||||||
|
- What's unclear: pg0 on macOS may have different startup characteristics
|
||||||
|
- Recommendation: Test first session start; check `hindsight-embed.log` for any pg0-related errors
|
||||||
|
|
||||||
|
## Environment Availability
|
||||||
|
|
||||||
|
| Dependency | Required By | Available | Version | Fallback |
|
||||||
|
|------------|------------|-----------|---------|----------|
|
||||||
|
| `uv` | Package installation | ✓ | 0.11.19 | `hermes memory setup` uses uv internally |
|
||||||
|
| Hermes venv (`~/.hermes/hermes-agent/venv/`) | Package target | ✓ | Python 3.11 | — |
|
||||||
|
| OpenRouter API key | LLM extraction | ✓ | `sk-or-v1-...` (existing) | — |
|
||||||
|
| `hindsight-all` pip package | Embedded daemon | ✓ installed | 0.8.2 | `hindsight-client` only (cloud mode) |
|
||||||
|
| Hermes gateway | Activation | ✓ | v0.16.0 | — |
|
||||||
|
|
||||||
|
**Missing dependencies with no fallback:** none
|
||||||
|
**Missing dependencies with fallback:** none
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
### Test Framework
|
||||||
|
| Property | Value |
|
||||||
|
|----------|-------|
|
||||||
|
| Framework | pytest |
|
||||||
|
| Config file | none — use `python -m pytest` |
|
||||||
|
| Quick run command | `python -m pytest tests/ -x -q` |
|
||||||
|
| Full suite command | `python -m pytest tests/` |
|
||||||
|
|
||||||
|
### Phase Requirements → Test Map
|
||||||
|
| Req ID | Behavior | Test Type | Automated Command | File Exists? |
|
||||||
|
|--------|----------|-----------|-------------------|-------------|
|
||||||
|
| MEM-01 | Hindsight config.json written with correct keys | smoke | Check file exists and JSON valid | ❌ Wave 0 |
|
||||||
|
| MEM-01 | `memory.provider` set to `hindsight` | smoke | `hermes config get memory.provider` | ❌ Wave 0 |
|
||||||
|
| MEM-01 | Hindsight daemon starts on session init | smoke | Check `hindsight-embed.log` for success | ❌ Wave 0 |
|
||||||
|
| MEM-01 | Recall works across sessions | integration | Manual test: store fact in session A, recall in session B | Manual only |
|
||||||
|
|
||||||
|
### Sampling Rate
|
||||||
|
- **Per task commit:** `check-config.sh` (verify config.json, env var, provider setting)
|
||||||
|
- **Per wave merge:** Full verification sequence
|
||||||
|
- **Phase gate:** Functional test: store + recall across two sessions
|
||||||
|
|
||||||
|
### Wave 0 Gaps
|
||||||
|
- [ ] `tests/test_hindsight_config.py` — validates config.json structure and env vars
|
||||||
|
- [ ] `tests/test_hindsight_install.py` — validates package installation in Hermes venv
|
||||||
|
|
||||||
|
*(See gsd-add-tests skill for generating these after implementation.)*
|
||||||
|
|
||||||
|
## Security Domain
|
||||||
|
|
||||||
|
> Required: `security_enforcement` not explicitly false.
|
||||||
|
|
||||||
|
### Applicable ASVS Categories
|
||||||
|
|
||||||
|
| ASVS Category | Applies | Standard Control |
|
||||||
|
|---------------|---------|-----------------|
|
||||||
|
| V2 Authentication | no | No new auth — reuses existing OpenRouter key |
|
||||||
|
| V3 Session Management | no | Hindsight sessions are managed by the plugin internally |
|
||||||
|
| V4 Access Control | no | No user-facing access control changes |
|
||||||
|
| V5 Input Validation | yes | LLM extraction output is validated by Hindsight server-side |
|
||||||
|
| V6 Cryptography | no | No new cryptographic operations |
|
||||||
|
| V8 Data Protection | yes | Local embedded mode = no external data send; data persists in local PostgreSQL |
|
||||||
|
|
||||||
|
### Known Threat Patterns for {Hermes + Hindsight}
|
||||||
|
|
||||||
|
| Pattern | STRIDE | Standard Mitigation |
|
||||||
|
|---------|--------|---------------------|
|
||||||
|
| Memory data exfiltration via prompt injection | Information Disclosure | Local embedded mode = no external network data send; data stays in local PostgreSQL. LLM extraction calls go through OpenRouter with only the conversation turn as payload |
|
||||||
|
| LLM extraction API key compromise | Tampering | OpenRouter key already stored in `~/.hermes/.env` with 0600 permissions; reused same key, no new secret to manage |
|
||||||
|
| Daemon port exposure (localhost:9177) | Elevation of Privilege | Daemon binds to localhost only; not exposed to Docker container or network |
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- `~/.hermes/hermes-agent/plugins/memory/hindsight/README.md` — Full configuration reference with all options, env vars, and modes [VERIFIED: read in full]
|
||||||
|
- `~/.hermes/hermes-agent/plugins/memory/hindsight/__init__.py` (1803 lines) — Complete MemoryProvider implementation; config loading, daemon management, client initialization [VERIFIED: read in full]
|
||||||
|
- `~/.hermes/hermes-agent/plugins/memory/__init__.py` — "Only ONE provider can be active at a time" constraint line 12 [VERIFIED: read provider conflict logic]
|
||||||
|
- `~/.hermes/hermes-agent/agent/memory_manager.py` lines 342-354 — Provider conflict implementation [VERIFIED: read]
|
||||||
|
- `~/.hermes/hermes-agent/hermes_cli/memory_setup.py` — Interactive setup wizard code [VERIFIED: read]
|
||||||
|
- `~/.hermes/hermes-agent/venv/` — Confirmed `hindsight-all==0.8.2` installed [VERIFIED: pip show]
|
||||||
|
- `~/.hermes/config.yaml` §memory — Current empty provider setting [VERIFIED: read]
|
||||||
|
- `~/.hermes/.env` — Existing OpenRouter API key [VERIFIED: read]
|
||||||
|
- `~/.hindsight/profiles/hermes.env` — Pre-existing daemon config from previous attempt [VERIFIED: read]
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- `~/.hermes/hermes-agent/pyproject.toml` — `hindsight-client==0.6.1` listed as extras dependency [VERIFIED: grep]
|
||||||
|
- CONTEXT.md D-01 through D-10 — Locked decisions for this phase [VERIFIED: read]
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence)
|
||||||
|
- Hindsight daemon behavior on macOS with pg0 — Not verified; will be validated during first session start [ASSUMED]
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH — all packages confirmed installed in Hermes venv; config structure verified against plugin source code
|
||||||
|
- Architecture: HIGH — plugin code read in full; data flow traced from agent loop to daemon
|
||||||
|
- Pitfalls: HIGH — each sourced from specific Hermes source lines and plugin implementation details
|
||||||
|
- Security: MEDIUM — local embedded mode eliminates external data send risk, but daemon behavior on macOS is not verified
|
||||||
|
|
||||||
|
**Research date:** 2026-06-14
|
||||||
|
**Valid until:** 2026-07-14 (30 days — stable Hermes v0.16.0 configuration)
|
||||||
390
.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md
Normal file
390
.planning/phases/06-default-repos-ssh-mount/06-01-PLAN.md
Normal file
@@ -0,0 +1,390 @@
|
|||||||
|
---
|
||||||
|
phase: 06-default-repos-ssh-mount
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- ~/.hermes/config.yaml
|
||||||
|
- ~/.hermes/.env
|
||||||
|
- ~/.hermes/scripts/session-init.sh
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- REPO-01
|
||||||
|
- REPO-02
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "New Hermes Docker container has SSH keys mounted at /root/.ssh/ — git clone to Bitbucket works without manual auth"
|
||||||
|
- "DEFAULT_REPOS (rai-ops, rai-deployment, rai-devtools) are available at /workspace/<name> in every new session"
|
||||||
|
- "Container restart does NOT lose repo mounts — repos persist because they're host-mounted, not cloned"
|
||||||
|
- "SSH credentials are mounted read-only inside Docker — agent cannot modify keys even as root"
|
||||||
|
- "session-init.sh logs repo mount status at shell start without blocking the agent prompt"
|
||||||
|
- "DEFAULT_REPOS is configurable via .env — user adds/removes repos by editing one variable + docker_volumes"
|
||||||
|
artifacts:
|
||||||
|
- path: "~/.hermes/scripts/session-init.sh"
|
||||||
|
provides: "Mount verification script for DEFAULT_REPOS at session start"
|
||||||
|
min_lines: 25
|
||||||
|
- path: "~/.hermes/config.yaml"
|
||||||
|
provides: "Docker volume mounts (SSH keys + repos), shell_init_files, docker_forward_env"
|
||||||
|
contains: "docker_volumes.*id_ed25519razer"
|
||||||
|
- path: "~/.hermes/.env"
|
||||||
|
provides: "DEFAULT_REPOS environment variable"
|
||||||
|
contains: "DEFAULT_REPOS"
|
||||||
|
key_links:
|
||||||
|
- from: "~/.hermes/config.yaml (docker_volumes)"
|
||||||
|
to: "~/.ssh/id_ed25519razer"
|
||||||
|
via: "volume mount"
|
||||||
|
pattern: "id_ed25519razer.*ro"
|
||||||
|
- from: "~/.hermes/config.yaml (docker_volumes)"
|
||||||
|
to: "~/Razer/rai-ops"
|
||||||
|
via: "volume mount"
|
||||||
|
pattern: "rai-ops.*rw"
|
||||||
|
- from: "~/.hermes/config.yaml (shell_init_files)"
|
||||||
|
to: "~/.hermes/scripts/session-init.sh"
|
||||||
|
via: "shell_init_files path"
|
||||||
|
pattern: "session-init.sh"
|
||||||
|
- from: "~/.hermes/.env"
|
||||||
|
to: "~/.hermes/config.yaml (docker_forward_env)"
|
||||||
|
via: "env forwarding"
|
||||||
|
pattern: "DEFAULT_REPOS"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Mount DEFAULT_REPOS (rai-ops, rai-deployment, rai-devtools) directly from host filesystem into every new Hermes Docker session with SSH credentials mounted read-only for git operations, plus a lightweight session-init.sh verification script.
|
||||||
|
|
||||||
|
Purpose: Eliminate the #1 user friction point — manually cloning repos every session. Host-direct mounts preserve existing git worktrees, branches, and uncommitted changes across container restarts (D-02). SSH keys are mounted per-file as read-only (`:ro`) to limit credential exposure (D-01).
|
||||||
|
|
||||||
|
Output:
|
||||||
|
- `~/.hermes/scripts/session-init.sh` — non-blocking mount verification script
|
||||||
|
- `~/.hermes/.env` — `DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools`
|
||||||
|
- `~/.hermes/config.yaml` — 4 SSH key mounts, 3 repo mounts, shell_init_files, docker_forward_env additions
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@/Users/bapung/.planning/PROJECT.md
|
||||||
|
@/Users/bapung/.planning/ROADMAP.md
|
||||||
|
|
||||||
|
# Current Hermes config for editing
|
||||||
|
@/Users/bapung/.hermes/config.yaml
|
||||||
|
@/Users/bapung/.hermes/.env
|
||||||
|
|
||||||
|
# SSH config for reference
|
||||||
|
@/Users/bapung/.ssh/config
|
||||||
|
|
||||||
|
# Existing scripts for shebang/pattern consistency
|
||||||
|
@/Users/bapung/.hermes/scripts/ngn-jira
|
||||||
|
@/Users/bapung/.hermes/scripts/ngn-bitbucket
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Create session-init.sh mount verification script</name>
|
||||||
|
<files>~/.hermes/scripts/session-init.sh</files>
|
||||||
|
<read_first>
|
||||||
|
~/.hermes/scripts/ngn-jira (shebang pattern)
|
||||||
|
~/.hermes/scripts/ngn-bitbucket (shebang pattern)
|
||||||
|
This file's RESEARCH.md §Code Examples for the reference implementation
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Create `~/.hermes/scripts/session-init.sh` with the following behavior (per D-03, D-04):
|
||||||
|
|
||||||
|
- **Shebang:** `#!/bin/bash`
|
||||||
|
- **set options:** `set -uo pipefail` — deliberately NOT `-e` so missing repos don't abort the script (non-blocking session start per D-03)
|
||||||
|
- **Reads DEFAULT_REPOS** from environment (forwarded via `docker_forward_env` per D-04)
|
||||||
|
- **Splits comma-separated list** and trims whitespace from each entry
|
||||||
|
- **For each repo:** checks `[ -d "/workspace/$repo/.git" ]` — confirm it's a valid git checkout, not just an empty dir
|
||||||
|
- **Logs:**
|
||||||
|
- `[session-init] ✓ repo — mounted at /workspace/repo` on success
|
||||||
|
- `[session-init] ⚠ repo — NOT FOUND at /workspace/repo` on failure
|
||||||
|
- **Summary:** "All DEFAULT_REPOS verified" vs "Some repos missing — check docker_volumes in config.yaml"
|
||||||
|
- **Exit code:** Always 0 — session starts regardless (discretion area)
|
||||||
|
- **Empty DEFAULT_REPOS guard:** If env var unset/empty, log "[session-init] DEFAULT_REPOS not set — skipping verification" and exit 0
|
||||||
|
|
||||||
|
**Do NOT** include:
|
||||||
|
- `set -e` — would abort on first missing repo (blocks session start)
|
||||||
|
- Any `git clone`, `git pull`, or network operations — script must complete in <1s (ROADMAP success criteria 5: 30s timeout)
|
||||||
|
- Any file writes or modifications — this is a read-only checker
|
||||||
|
|
||||||
|
Make the script executable: `chmod +x ~/.hermes/scripts/session-init.sh`
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>bash -n ~/.hermes/scripts/session-init.sh 2>&1; chmod +x ~/.hermes/scripts/session-init.sh 2>/dev/null; stat -f "%Sp" ~/.hermes/scripts/session-init.sh | grep -q -- "-rwx"</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
1. Script passes bash syntax check (`bash -n` exits 0)
|
||||||
|
2. Script is executable (`stat` shows `-rwx` permissions)
|
||||||
|
3. Logic: does NOT use `set -e`, DOES use `set -uo pipefail`
|
||||||
|
4. Guard: if DEFAULT_REPOS unset, exits 0 with log message (not error)
|
||||||
|
5. Verification: `test -d "/workspace/$repo/.git"` (not just `test -d "/workspace/$repo"`)
|
||||||
|
6. Always exits 0 regardless of mount status
|
||||||
|
</acceptance_criteria>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Add DEFAULT_REPOS to .env and update config.yaml with mounts + shell_init_files + forward_env</name>
|
||||||
|
<files>~/.hermes/.env, ~/.hermes/config.yaml</files>
|
||||||
|
<read_first>
|
||||||
|
~/.hermes/.env (full file — already read in planning context)
|
||||||
|
~/.hermes/config.yaml (full file — already read in planning context)
|
||||||
|
The RESEARCH.md §Code Examples for exact mount strings
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Make the following changes:
|
||||||
|
|
||||||
|
### A. `~/.hermes/.env` — Append DEFAULT_REPOS
|
||||||
|
At the tail of the file, before the last line (HINDSIGHT_LLM_API_KEY) or after it, add:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# DEFAULT_REPOS — repos mounted into every session workspace
|
||||||
|
# Comma-separated list. Each repo must have a matching docker_volume entry in config.yaml.
|
||||||
|
DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools
|
||||||
|
```
|
||||||
|
|
||||||
|
Place this after the LAST existing section (after the "# ngn-agent: OpenRouter fallback" block, near where JIRA_API_TOKEN and HINDSIGHT_LLM_API_KEY are). Group it with the other ngn-agent-specific vars.
|
||||||
|
|
||||||
|
### B. `~/.hermes/config.yaml` — Append SSH key mounts to `terminal.docker_volumes`
|
||||||
|
Add these 4 entries to the `docker_volumes` list (after the existing scripts mount). Each entry is a quoted string. Per D-01:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
terminal:
|
||||||
|
docker_volumes:
|
||||||
|
# ... existing entries remain unchanged ...
|
||||||
|
# SSH key mounts (read-only — per D-01)
|
||||||
|
- /Users/bapung/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro
|
||||||
|
- /Users/bapung/.ssh/id_rsa:/root/.ssh/id_rsa:ro
|
||||||
|
- /Users/bapung/.ssh/config:/root/.ssh/config:ro
|
||||||
|
- /Users/bapung/.ssh/known_hosts:/root/.ssh/known_hosts:ro
|
||||||
|
# Repo mounts (read-write — per D-02)
|
||||||
|
- /Users/bapung/Razer/rai-ops:/workspace/rai-ops:rw
|
||||||
|
- /Users/bapung/Razer/rai-deployment:/workspace/rai-deployment:rw
|
||||||
|
- /Users/bapung/Razer/rai-devtools:/workspace/rai-devtools:rw
|
||||||
|
```
|
||||||
|
|
||||||
|
**WHY `known_hosts` is included (research finding):** Without `/root/.ssh/known_hosts`, SSH prompts interactively for host key confirmation on first Bitbucket connection. Since the container is non-interactive, this hangs the clone. The host's `~/.ssh/known_hosts` already contains bitbucket.org host keys (verified: 3 keys present).
|
||||||
|
|
||||||
|
### C. `~/.hermes/config.yaml` — Set `terminal.shell_init_files`
|
||||||
|
Change from `shell_init_files: []` to:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
terminal:
|
||||||
|
shell_init_files:
|
||||||
|
- /usr/local/bin/session-init.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Per D-03. The `~/.hermes/scripts/` directory is already mounted to `/usr/local/bin:ro`, so `/usr/local/bin/session-init.sh` resolves automatically.
|
||||||
|
|
||||||
|
### D. `~/.hermes/config.yaml` — Append `DEFAULT_REPOS` to `terminal.docker_forward_env`
|
||||||
|
Change from `docker_forward_env: [JIRA_EMAIL, JIRA_API_TOKEN]` to include `DEFAULT_REPOS`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
terminal:
|
||||||
|
docker_forward_env:
|
||||||
|
- JIRA_EMAIL
|
||||||
|
- JIRA_API_TOKEN
|
||||||
|
- DEFAULT_REPOS # per D-04
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important YAML formatting rules:**
|
||||||
|
- `docker_volumes` entries are list items under the existing key — append to the existing list, do NOT replace the entire list.
|
||||||
|
- `shell_init_files` replaces the existing empty list `[]` with a new list containing one item.
|
||||||
|
- `docker_forward_env` appends to the existing list — do NOT replace the existing entries.
|
||||||
|
- Maintain consistent indentation (2-space YAML).
|
||||||
|
- Preserve all other config keys untouched.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>
|
||||||
|
# Verify config.yaml is valid YAML
|
||||||
|
python3 -c "import yaml; c=yaml.safe_load(open('$HOME/.hermes/config.yaml')); volumes=c['terminal']['docker_volumes']; assert any('id_ed25519razer' in v for v in volumes), 'SSH key mount missing'; assert any('rai-ops' in v for v in volumes), 'Repo mount missing'; assert c['terminal']['shell_init_files'] == ['/usr/local/bin/session-init.sh'], 'shell_init_files wrong'; assert 'DEFAULT_REPOS' in c['terminal']['docker_forward_env'], 'DEFAULT_REPOS not in forward_env'" 2>&1
|
||||||
|
|
||||||
|
# Verify .env has DEFAULT_REPOS
|
||||||
|
grep -q 'DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools' $HOME/.hermes/.env && echo "ENV: OK" || echo "ENV: MISSING"
|
||||||
|
|
||||||
|
# Verify SSH key entries are :ro (read-only)
|
||||||
|
python3 -c "import yaml; c=yaml.safe_load(open('$HOME/.hermes/config.yaml')); vols=c['terminal']['docker_volumes']; ro=[v for v in vols if 'id_ed25519razer' in v]; assert ro and ro[0].endswith(':ro'), f'SSH key not :ro: {ro}'" 2>&1
|
||||||
|
|
||||||
|
# Verify repo entries are :rw (read-write)
|
||||||
|
python3 -c "import yaml; c=yaml.safe_load(open('$HOME/.hermes/config.yaml')); vols=c['terminal']['docker_volumes']; rw=[v for v in vols if 'rai-ops' in v]; assert rw and rw[0].endswith(':rw'), f'Repo mount not :rw: {rw}'" 2>&1
|
||||||
|
</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
1. `config.yaml` has 4 SSH key mount entries under `docker_volumes`, all `:ro`
|
||||||
|
2. `config.yaml` has 3 repo mount entries (`rai-ops`, `rai-deployment`, `rai-devtools`) under `docker_volumes`, all `:rw`
|
||||||
|
3. `config.yaml` `shell_init_files` is set to `["/usr/local/bin/session-init.sh"]`
|
||||||
|
4. `config.yaml` `docker_forward_env` includes `DEFAULT_REPOS` alongside `JIRA_EMAIL` and `JIRA_API_TOKEN`
|
||||||
|
5. `.env` has `DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools`
|
||||||
|
6. All YAML is valid (python yaml parser confirms)
|
||||||
|
7. Original config entries are preserved (not deleted)
|
||||||
|
</acceptance_criteria>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Verify end-to-end — SSH auth, repo mounts, script execution via Docker test</name>
|
||||||
|
<files>(no files modified — verification only)</files>
|
||||||
|
<read_first>
|
||||||
|
Phase research §Verified Findings (SSH auth, subpath mounts, known_hosts requirements)
|
||||||
|
</read_first>
|
||||||
|
<action>
|
||||||
|
Run a Docker test container with the EXACT volume mounts defined in Task 2 to verify everything works end-to-end before the agent needs it.
|
||||||
|
|
||||||
|
Use the nikolaik/python-nodejs:python3.11-nodejs20 image (same as Hermes uses).
|
||||||
|
|
||||||
|
Docker command:
|
||||||
|
```bash
|
||||||
|
docker run --rm \
|
||||||
|
-v ~/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro \
|
||||||
|
-v ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro \
|
||||||
|
-v ~/.ssh/config:/root/.ssh/config:ro \
|
||||||
|
-v ~/.ssh/known_hosts:/root/.ssh/known_hosts:ro \
|
||||||
|
-v ~/Razer/rai-ops:/workspace/rai-ops:rw \
|
||||||
|
-v ~/Razer/rai-deployment:/workspace/rai-deployment:rw \
|
||||||
|
-v ~/Razer/rai-devtools:/workspace/rai-devtools:rw \
|
||||||
|
-v ~/.hermes/scripts:/usr/local/bin:ro \
|
||||||
|
-e DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools \
|
||||||
|
nikolaik/python-nodejs:python3.11-nodejs20 \
|
||||||
|
bash -c '
|
||||||
|
echo "=== 1. SSH Keys Mounted ==="
|
||||||
|
ls -la /root/.ssh/ && echo ""
|
||||||
|
|
||||||
|
echo "=== 2. SSH Authentication ==="
|
||||||
|
ssh -T git@bitbucket.org 2>&1 || true && echo ""
|
||||||
|
|
||||||
|
echo "=== 3. Repo Mounts ==="
|
||||||
|
for repo in rai-ops rai-deployment rai-devtools; do
|
||||||
|
if [ -d "/workspace/$repo/.git" ]; then
|
||||||
|
echo " ✓ $repo — mounted git repo"
|
||||||
|
else
|
||||||
|
echo " ✗ $repo — NOT FOUND"
|
||||||
|
fi
|
||||||
|
done && echo ""
|
||||||
|
|
||||||
|
echo "=== 4. session-init.sh ==="
|
||||||
|
session-init.sh && echo "exit code: $?" && echo ""
|
||||||
|
|
||||||
|
echo "=== 5. On-Demand Clone (REPO-02) ==="
|
||||||
|
git clone --depth 1 git@bitbucket.org:razersw/rai-ansible.git /workspace/rai-ansible 2>&1 && \
|
||||||
|
echo " ✓ Clone succeeded" || echo " ✗ Clone failed"
|
||||||
|
'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Expected output:**
|
||||||
|
- SSH keys present at `/root/.ssh/` with correct permissions
|
||||||
|
- `ssh -T git@bitbucket.org` returns "authenticated via ssh key" (exit code 1 from SSH is expected — this is auth success, just no shell)
|
||||||
|
- All 3 repos show `✓ repo — mounted git repo`
|
||||||
|
- `session-init.sh` runs without error, shows all repos verified
|
||||||
|
- `git clone` of an additional repo succeeds
|
||||||
|
|
||||||
|
**If ANY check fails:** Diagnose and fix:
|
||||||
|
- SSH auth fails: check key permissions (need `600`/`700`), check `known_hosts` has bitbucket.org entries
|
||||||
|
- Repo mount fails: verify the repo directories exist on host at `~/Razer/<name>/`
|
||||||
|
- session-init.sh fails: debug the script logic
|
||||||
|
- Clone fails: check SSH config file mounting
|
||||||
|
|
||||||
|
Also verify the parent `/workspace` mount mode (research Pitfall 1):
|
||||||
|
```bash
|
||||||
|
docker run --rm \
|
||||||
|
-v /Users/bapung/Razer/ngn-agent:/workspace:rw \
|
||||||
|
nikolaik/python-nodejs:python3.11-nodejs20 \
|
||||||
|
mount | grep /workspace
|
||||||
|
```
|
||||||
|
This should show `rw` — NOT `ro`. If `ro`, the parent mount blocks subpath volume creation and the repo mounts will fail at container start.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>
|
||||||
|
# Run the verification and check output for success signals
|
||||||
|
TEST_OUTPUT=$(docker run --rm \
|
||||||
|
-v ~/.ssh/id_ed25519razer:/root/.ssh/id_ed25519razer:ro \
|
||||||
|
-v ~/.ssh/id_rsa:/root/.ssh/id_rsa:ro \
|
||||||
|
-v ~/.ssh/config:/root/.ssh/config:ro \
|
||||||
|
-v ~/.ssh/known_hosts:/root/.ssh/known_hosts:ro \
|
||||||
|
-v ~/Razer/rai-ops:/workspace/rai-ops:rw \
|
||||||
|
-v ~/Razer/rai-deployment:/workspace/rai-deployment:rw \
|
||||||
|
-v ~/Razer/rai-devtools:/workspace/rai-devtools:rw \
|
||||||
|
-v ~/.hermes/scripts:/usr/local/bin:ro \
|
||||||
|
-e DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools \
|
||||||
|
nikolaik/python-nodejs:python3.11-nodejs20 \
|
||||||
|
bash -c 'ssh -T git@bitbucket.org 2>&1 | grep -q "authenticated" && echo "SSH_AUTH_OK" || echo "SSH_AUTH_FAIL"; for r in rai-ops rai-deployment rai-devtools; do [ -d "/workspace/$r/.git" ] && echo "REPO_${r}_OK" || echo "REPO_${r}_FAIL"; done; session-init.sh 2>&1 | grep -q "All DEFAULT_REPOS verified" && echo "SCRIPT_OK" || echo "SCRIPT_FAIL"; git clone --depth 1 git@bitbucket.org:razersw/rai-ansible.git /tmp/test-clone 2>&1 | grep -q "done" && echo "CLONE_OK" || echo "CLONE_FAIL"' 2>&1)
|
||||||
|
echo "$TEST_OUTPUT"
|
||||||
|
echo "$TEST_OUTPUT" | grep -q "SSH_AUTH_OK" && echo "PASS: SSH auth" || echo "FAIL: SSH auth"
|
||||||
|
echo "$TEST_OUTPUT" | grep -q "REPO_rai-ops_OK" && echo "PASS: rai-ops mounted" || echo "FAIL: rai-ops mounted"
|
||||||
|
echo "$TEST_OUTPUT" | grep -q "REPO_rai-deployment_OK" && echo "PASS: rai-deployment mounted" || echo "FAIL: rai-deployment mounted"
|
||||||
|
echo "$TEST_OUTPUT" | grep -q "REPO_rai-devtools_OK" && echo "PASS: rai-devtools mounted" || echo "FAIL: rai-devtools mounted"
|
||||||
|
echo "$TEST_OUTPUT" | grep -q "SCRIPT_OK" && echo "PASS: session-init.sh" || echo "FAIL: session-init.sh"
|
||||||
|
echo "$TEST_OUTPUT" | grep -q "CLONE_OK" && echo "PASS: on-demand clone" || echo "FAIL: on-demand clone"
|
||||||
|
</automated>
|
||||||
|
</verify>
|
||||||
|
<acceptance_criteria>
|
||||||
|
1. SSH auth succeeds — `authenticated via ssh key` response from Bitbucket
|
||||||
|
2. All 3 DEFAULT_REPOS are mounted git repos at `/workspace/<name>` (`.git` directory present)
|
||||||
|
3. `session-init.sh` runs and reports all repos verified
|
||||||
|
4. On-demand `git clone` of additional repo succeeds (verifies REPO-02 capability)
|
||||||
|
5. Parent `/workspace` mount is `:rw` (not `:ro`) — verified by mount table inspection
|
||||||
|
</acceptance_criteria>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Assets
|
||||||
|
| Asset | Sensitivity | Protection |
|
||||||
|
|-------|-------------|------------|
|
||||||
|
| SSH private keys (id_ed25519razer, id_rsa) | HIGH — Bitbucket access | Docker `:ro` mount prevents modification. Per-file mount (not full `~/.ssh/`) limits blast radius. |
|
||||||
|
| SSH config | MEDIUM — auth routing info | Mounted `:ro` — read-only in container. |
|
||||||
|
| Repo source code (rai-ops, rai-deployment, rai-devtools) | HIGH — proprietary code | Mounted `:rw` intentionally — agent needs to commit/push. Same exposure as having repo on host. |
|
||||||
|
| `known_hosts` | LOW — public host keys | Mounted `:ro`. Read-only, prevents MitM host key injection. |
|
||||||
|
|
||||||
|
## Trust Boundaries
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Host → Docker container | SSH keys cross from macOS host into container via volume mount. Docker enforces read-only at VFS level. |
|
||||||
|
| Container → Bitbucket | Agent inside container authenticates to Bitbucket via mounted SSH keys. Network boundary. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-06-01 | Information Disclosure | SSH keys in Docker | mitigate | Keys mounted `:ro` — agent can read (required for auth) but cannot modify. Per-file mount (not full `~/.ssh/`) limits exposure to only the two declared keys. |
|
||||||
|
| T-06-02 | Spoofing | Bitbucket auth via stolen key | accept | Key is user's personal SSH key. Deferred to future phase: per-repo deploy keys (deferred idea in CONTEXT.md). Current mitigation: key can only be read inside container, not exfiltrated to external hosts. |
|
||||||
|
| T-06-03 | Tampering | Repo source code | accept | Repos mounted `:rw` by design — agent must create branches, commit, push. Same trust model as developer's local environment. |
|
||||||
|
| T-06-04 | Tampering | `known_hosts` | mitigate | File mounted `:ro` — prevents agent from injecting fraudulent host keys. File is from trusted host source. |
|
||||||
|
| T-06-SC | Tampering | Package installs | n/a | Zero packages installed. All changes are config edits + one shell script. |
|
||||||
|
|
||||||
|
## Residual Risk
|
||||||
|
- SSH key is readable by agent inside container. If the agent is compromised via prompt injection, the key could be read. Mitigated by: (a) key permits only Bitbucket read/write to known repos, (b) no other services use the same key, (c) per-repo deploy keys deferred to future phase.
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. All 4 Python-based YAML assertions pass for config.yaml validity
|
||||||
|
2. `grep` confirms DEFAULT_REPOS in .env
|
||||||
|
3. Docker test container confirms SSH auth, repo mounts, script execution, and on-demand clone
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
1. ✅ SSH keys mounted (`:ro`) and Bitbucket auth works inside Docker (verified by container test)
|
||||||
|
2. ✅ All 3 DEFAULT_REPOS mounted at `/workspace/<name>` (verified by container test)
|
||||||
|
3. ✅ `session-init.sh` runs at shell start and reports mount status (verified by container test)
|
||||||
|
4. ✅ On-demand git clone works inside Docker (verified by container test)
|
||||||
|
5. ✅ `DEFAULT_REPOS` configurable via `.env` — one variable to add/remove repos
|
||||||
|
6. ✅ All changes are additive (no existing config removed)
|
||||||
|
7. ✅ Parent `/workspace` mount confirmed `:rw` (subpath volumes will work)
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
## Artifacts This Phase Produces
|
||||||
|
|
||||||
|
| Artifact | Path | Type | Purpose |
|
||||||
|
|----------|------|------|---------|
|
||||||
|
| session-init.sh | `~/.hermes/scripts/session-init.sh` | Shell script | Verifies DEFAULT_REPOS mounts at session start |
|
||||||
|
| DEFAULT_REPOS env var | `~/.hermes/.env` | Config | Defines which repos to verify |
|
||||||
|
| SSH key mounts (4) | `~/.hermes/config.yaml` → `docker_volumes` | Config | Mounts id_ed25519razer, id_rsa, config, known_hosts as `:ro` |
|
||||||
|
| Repo mounts (3) | `~/.hermes/config.yaml` → `docker_volumes` | Config | Mounts rai-ops, rai-deployment, rai-devtools as `:rw` |
|
||||||
|
| shell_init_files entry | `~/.hermes/config.yaml` | Config | Triggers session-init.sh at shell start |
|
||||||
|
| docker_forward_env entry | `~/.hermes/config.yaml` | Config | Forwards DEFAULT_REPOS into container |
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md` when done
|
||||||
|
</output>
|
||||||
143
.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md
Normal file
143
.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
---
|
||||||
|
phase: 06-default-repos-ssh-mount
|
||||||
|
plan: 01
|
||||||
|
subsystem: infra
|
||||||
|
tags: docker, ssh, volume-mounts, hermes, git, bitbucket
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 05-hermes-memory-hindsight
|
||||||
|
provides: Hermes config structure, .env conventions
|
||||||
|
provides:
|
||||||
|
- SSH key mounts for Bitbucket git auth
|
||||||
|
- 3 default repo mounts (rai-ops, rai-deployment, rai-devtools)
|
||||||
|
- session-init.sh mount verification script
|
||||||
|
- DEFAULT_REPOS environment variable
|
||||||
|
|
||||||
|
affects: [07-session-skill, 08-cron-reporting]
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added:
|
||||||
|
- session-init.sh (bash script)
|
||||||
|
patterns:
|
||||||
|
- Per-file SSH key mounts (`:ro`) instead of full directory mount
|
||||||
|
- Non-blocking shell init scripts (no `set -e`)
|
||||||
|
- Subpath volume mounts with `:rw` parent dependency
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- ~/.hermes/scripts/session-init.sh
|
||||||
|
modified:
|
||||||
|
- ~/.hermes/.env
|
||||||
|
- ~/.hermes/config.yaml
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Mounted SSH keys per-file (`:ro`) rather than full `~/.ssh/` directory — limits credential exposure to only id_ed25519razer and id_rsa"
|
||||||
|
- "Mounted repos directly from host (`:rw`) instead of cloning inside container — preserves git worktrees, branches, uncommitted changes"
|
||||||
|
- "Included `known_hosts` mount — prevents SSH host key prompt from blocking non-interactive git operations"
|
||||||
|
- "session-init.sh uses `set -uo pipefail` (not `-e`) — session starts even if repos are missing"
|
||||||
|
- "Parent `/workspace` mount verified `:rw` — subpath volume mounts work correctly"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Pattern 1: Per-file credential mounts for limited security boundary"
|
||||||
|
- "Pattern 2: Non-blocking init scripts with graceful degradation"
|
||||||
|
|
||||||
|
requirements-completed: [REPO-01, REPO-02]
|
||||||
|
|
||||||
|
duration: 2 min
|
||||||
|
completed: 2026-06-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 6 Plan 1: Default Repos & SSH Mount Summary
|
||||||
|
|
||||||
|
**SSH key mounts for Bitbucket auth, 3 default repo mounts (rai-ops, rai-deployment, rai-devtools), and session-init.sh non-blocking verification script — all verified end-to-end via Docker test container**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 2 min
|
||||||
|
- **Started:** 2026-06-15T12:03:47Z
|
||||||
|
- **Completed:** 2026-06-15T12:05:58Z
|
||||||
|
- **Tasks:** 3
|
||||||
|
- **Files modified:** 3
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- SSH keys (`id_ed25519razer`, `id_rsa`, `config`, `known_hosts`) mounted read-only into Docker — Bitbucket auth verified: "authenticated via ssh key"
|
||||||
|
- 3 default repos (rai-ops, rai-deployment, rai-devtools) mounted at `/workspace/<name>` with `:rw` — no re-cloning needed across sessions
|
||||||
|
- `session-init.sh` created in `~/.hermes/scripts/` — non-blocking verification at shell start, triggered via `shell_init_files`
|
||||||
|
- `DEFAULT_REPOS` env var added to `.env` and forwarded into container via `docker_forward_env`
|
||||||
|
- On-demand git clone verified working (REPO-02 capability)
|
||||||
|
- Parent `/workspace` mount confirmed `:rw` — subpath volumes will not fail
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Create session-init.sh script** — `ea56c05` (feat)
|
||||||
|
2. **Task 2: Update .env and config.yaml** — `2c3e96b` (feat)
|
||||||
|
3. **Task 3: Verify end-to-end Docker test** — `2ca590e` (test)
|
||||||
|
|
||||||
|
**Plan metadata:** (committed with SUMMARY below)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `~/.hermes/scripts/session-init.sh` — Non-blocking mount verification script (25 lines)
|
||||||
|
- `~/.hermes/.env` — Added `DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools`
|
||||||
|
- `~/.hermes/config.yaml` — Added 4 SSH key mounts (`:ro`), 3 repo mounts (`:rw`), `shell_init_files`, `docker_forward_env` entry
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **Per-file SSH key mounts** over full `~/.ssh/` directory mount — limits credential exposure to only the keys the agent needs (id_ed25519razer, id_rsa)
|
||||||
|
- **`known_hosts` included** — without it, SSH prompts for host key confirmation and hangs in non-interactive container; host already has bitbucket.org keys
|
||||||
|
- **session-init.sh uses `set -uo pipefail`** (not `-e`) — missing repos won't abort session start
|
||||||
|
- **Host-direct repo mounts** (`:rw`) instead of cloning — preserves existing worktrees, branches, and is not lost on container restart
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] `rai-ansible` repo inaccessible for clone test**
|
||||||
|
- **Found during:** Task 3 (End-to-end verification)
|
||||||
|
- **Issue:** Plan specified `rai-ansible` for on-demand clone test, but this repo does not exist or the SSH key lacks access
|
||||||
|
- **Fix:** Used `rai-ops` (known accessible repo) for clone test, cloning to a different path (`/tmp/rai-ops-test`)
|
||||||
|
- **Files modified:** None (verification only)
|
||||||
|
- **Verification:** Clone succeeded, git repo contents visible
|
||||||
|
- **Committed in:** `2ca590e` (Task 3 commit)
|
||||||
|
|
||||||
|
**2. [Rule 3 - Blocking] Python `yaml` module not installed for validation**
|
||||||
|
- **Found during:** Task 2 (config.yaml verification)
|
||||||
|
- **Issue:** Python yaml module not available on host, blocking automated YAML validation
|
||||||
|
- **Fix:** Installed pyyaml 6.0.3 via pip3
|
||||||
|
- **Files modified:** None (host package, not in repo)
|
||||||
|
- **Verification:** All 10 YAML assertions passed
|
||||||
|
- **Committed in:** `2c3e96b` (Task 2 commit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 2 auto-fixed (2 blocking)
|
||||||
|
**Impact on plan:** Both deviations minor — clone test used correct accessible repo, pyyaml installed temporarily for validation. No scope creep.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
- `rai-ansible` repo not accessible to the SSH key — used `rai-ops` cloned to alternate path instead. SSH auth itself is confirmed working.
|
||||||
|
- Python `yaml` module not installed on host — installed pyyaml for config validation.
|
||||||
|
- No pre-existing issues found.
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - no external service configuration required. SSH keys and repos already exist on the host filesystem. Changes to `~/.hermes/config.yaml` and `~/.hermes/.env` are ready for next Hermes session.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- SSH auth and repo mounts fully verified — ready for Phase 7 (session skill)
|
||||||
|
- `session-init.sh` provides lightweight mount verification at shell start
|
||||||
|
- `DEFAULT_REPOS` is configurable via `.env` — user edits one variable + docker_volumes to add/remove repos
|
||||||
|
- On-demand clone capability verified — agent can clone additional repos during sessions
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
All commits verified, all files exist, all acceptance criteria met.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 06-default-repos-ssh-mount*
|
||||||
|
*Completed: 2026-06-15*
|
||||||
297
.planning/phases/07-main-session-skill/07-01-PLAN.md
Normal file
297
.planning/phases/07-main-session-skill/07-01-PLAN.md
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
---
|
||||||
|
phase: 07-main-session-skill
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified: [~/.hermes/skills/ngn-agent/session/SKILL.md]
|
||||||
|
autonomous: true
|
||||||
|
requirements: [SKIL-04]
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- Agent detects similar previous sessions via hindsight_recall on session start and presents matches with resume/fresh option
|
||||||
|
- Agent creates a Jira Task ticket when user requests it, with configurable project and optional epic from cached list
|
||||||
|
- Agent searches and loads Confluence docs by ngn-agent tag when user requests it
|
||||||
|
- Agent prompts for Jira/Confluence updates at session end (user confirms before any mutation)
|
||||||
|
- Session summary is automatically saved to hindsight at session end (no prompt — unconditional per D-12)
|
||||||
|
- Future sessions can recall this session via hindsight_recall of the saved summary
|
||||||
|
artifacts:
|
||||||
|
- path: ~/.hermes/skills/ngn-agent/session/SKILL.md
|
||||||
|
provides: Full session lifecycle orchestration instructions in Hermes SKILL.md format
|
||||||
|
min_lines: 180
|
||||||
|
key_links:
|
||||||
|
- from: session/SKILL.md Procedure Step 1
|
||||||
|
to: hindsight_recall tool
|
||||||
|
via: Tool call instruction
|
||||||
|
pattern: hindsight_recall
|
||||||
|
- from: session/SKILL.md Procedure Step 2
|
||||||
|
to: ngn-jira POST /rest/api/3/issue
|
||||||
|
via: CLI command in Procedure
|
||||||
|
pattern: ngn-jira.*POST.*issue
|
||||||
|
- from: session/SKILL.md Procedure Step 2b
|
||||||
|
to: hindsight_retain (epic cache)
|
||||||
|
via: Tool call for caching epics
|
||||||
|
pattern: hindsight_retain.*epic
|
||||||
|
- from: session/SKILL.md Procedure Step 3
|
||||||
|
to: ngn-confluence GET /rest/api/search
|
||||||
|
via: CLI command tagged search
|
||||||
|
pattern: ngn-confluence.*tag
|
||||||
|
- from: session/SKILL.md Procedure Step 5
|
||||||
|
to: ngn-jira POST comment
|
||||||
|
via: CLI command for comment
|
||||||
|
pattern: ngn-jira.*comment
|
||||||
|
- from: session/SKILL.md Procedure Step 7
|
||||||
|
to: hindsight_retain tool (session summary)
|
||||||
|
via: Unconditional tool call
|
||||||
|
pattern: hindsight_retain.*session-summary
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Create the main ngn-agent session orchestration skill — a Hermes SKILL.md file that guides the agent through the full session lifecycle: detect similar previous sessions, prompt for Jira ticket and Confluence docs, work, and update/save at session end.
|
||||||
|
|
||||||
|
**Purpose:** Replace the ad-hoc per-session workflow with a standardized, repeatable session lifecycle covering the `initial-plan.md` `func session()` workflow. Every session follows the same init→work→close pattern regardless of task.
|
||||||
|
|
||||||
|
**Output:** `~/.hermes/skills/ngn-agent/session/SKILL.md` — one file, zero code/installs/config changes.
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
# Containing Artifacts for This Phase
|
||||||
|
|
||||||
|
@/Users/bapung/.planning/ROADMAP.md
|
||||||
|
@/Users/bapung/.planning/REQUIREMENTS.md
|
||||||
|
@/Users/bapung/.planning/phases/07-main-session-skill/07-CONTEXT.md
|
||||||
|
@/Users/bapung/.planning/phases/07-main-session-skill/07-RESEARCH.md
|
||||||
|
@/Users/bapung/Razer/ngn-agent/initial-plan.md
|
||||||
|
|
||||||
|
# Existing Skill Format References (4 patterns to follow)
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/aws-diagnostics/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/jira/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/confluence/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/bitbucket/SKILL.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Create session/SKILL.md with full session lifecycle procedure</name>
|
||||||
|
|
||||||
|
<read_first>
|
||||||
|
- 07-RESEARCH.md §"Example 1: session/SKILL.md (Full Content)" for the reference implementation
|
||||||
|
- 07-CONTEXT.md for all locked decisions (D-01 through D-14) to embed in the skill
|
||||||
|
- aws-diagnostics/SKILL.md, jira/SKILL.md, confluence/SKILL.md, bitbucket/SKILL.md for format fidelity
|
||||||
|
- initial-plan.md for the original func session() workflow
|
||||||
|
</read_first>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
~/.hermes/skills/ngn-agent/session/SKILL.md
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<action>
|
||||||
|
Create the directory `~/.hermes/skills/ngn-agent/session/` and write `SKILL.md` with:
|
||||||
|
|
||||||
|
**Frontmatter (per D-14 + existing skill convention):**
|
||||||
|
- `name: session` (matching directory name for clean `skill_view("session")` discovery)
|
||||||
|
- `description: "Main ngn-agent session lifecycle — init, work, close"`
|
||||||
|
- `tags: [ngn-agent, platform-engineering, session]`
|
||||||
|
- `category: devops`, `requires_toolsets: [terminal]`
|
||||||
|
- `version: 1.0.0`
|
||||||
|
|
||||||
|
**Sections (follow exact existing skill structure):**
|
||||||
|
|
||||||
|
### When to Use
|
||||||
|
Broad trigger conditions: "Load this skill at the START of EVERY platform engineering session, before any other work. This skill defines the standard session workflow." Include specific trigger examples (infrastructure task, Jira ticket creation, Confluence doc lookup, session end wrap-up).
|
||||||
|
|
||||||
|
### Important
|
||||||
|
- "Keep this skill loaded for the entire session" — note about re-loading via `skill_view("session")` if evicted
|
||||||
|
- D-02: Never create Jira tickets without asking first
|
||||||
|
- D-11: Never update Confluence without asking first
|
||||||
|
- D-12: Always save session summary to hindsight at end (no prompt)
|
||||||
|
- D-08: User must confirm before any Jira mutation
|
||||||
|
- Repos already mounted at `/workspace/` from Phase 6
|
||||||
|
|
||||||
|
### Procedure (7 steps — map to D-01 through D-13)
|
||||||
|
|
||||||
|
**Step 1: Check for Similar Previous Sessions (D-01)**
|
||||||
|
- Call `hindsight_recall` with query describing user's task, budget low
|
||||||
|
- Present matches in format: "Found [N] similar sessions from the last 2 weeks: [Title — Date — snippet]"
|
||||||
|
- Ask: resume (load context) or start fresh (proceed to step 2)
|
||||||
|
|
||||||
|
**Step 2: Prompt for Jira Ticket Creation (D-02, D-05, D-06, D-07)**
|
||||||
|
- Ask user if they want a Task ticket
|
||||||
|
- If yes:
|
||||||
|
1. Ask which Jira project
|
||||||
|
2. Check hindsight for cached epics (`hindsight_recall` query "jira epics cached"). If cache >24h old or user says it's wrong, refresh via `ngn-jira GET` and save via `hindsight_retain` with `tier: "epic-cache"`
|
||||||
|
3. Present cached epics, ask if user wants to set parent epic
|
||||||
|
4. Create Task via `ngn-jira POST /rest/api/3/issue` with project, summary, issuetype "Task", optional parent
|
||||||
|
5. Note the ticket key for session-end steps
|
||||||
|
- If no: proceed to step 3
|
||||||
|
|
||||||
|
**Step 3: Prompt for Confluence Docs (D-03, D-09, D-10)**
|
||||||
|
- Ask user if they want to load relevant docs
|
||||||
|
- If yes:
|
||||||
|
1. Search by tag via `ngn-confluence GET /rest/api/search?cql=tag="ngn-agent"`
|
||||||
|
2. Present matching pages (title, space, last modified)
|
||||||
|
3. Ask which pages to load
|
||||||
|
4. Load selected page content (expand=body.storage)
|
||||||
|
- If no: proceed to step 4
|
||||||
|
|
||||||
|
**Step 4: Work Phase (D-04)**
|
||||||
|
- Repos already mounted at `/workspace/` (rai-ops, rai-deployment, rai-devtools)
|
||||||
|
- Additional repo cloning: `git clone git@bitbucket.org:razersw/<repo>.git /workspace/<repo>`
|
||||||
|
- Session skill remains loaded for session-end steps
|
||||||
|
|
||||||
|
**Step 5: Session-End — Update Jira (D-08, D-12)**
|
||||||
|
- When user indicates work is complete: ask "Update Jira with summary comment?"
|
||||||
|
- If yes (and ticket exists): `ngn-jira POST /rest/api/3/issue/<KEY>/comment` with summary body
|
||||||
|
- Do NOT transition tickets without explicit user confirmation
|
||||||
|
|
||||||
|
**Step 6: Session-End — Update Confluence (D-11, D-12)**
|
||||||
|
- Ask "Create or update Confluence page documenting this session?"
|
||||||
|
- If yes: POST new page (with `labels: [{"name": "ngn-agent"}]`) or PUT update to existing
|
||||||
|
- If no: proceed
|
||||||
|
|
||||||
|
**Step 7: Save Session Summary to Hindsight (D-12, D-13)**
|
||||||
|
- **Automatic — no prompt.** Always save.
|
||||||
|
- Call `hindsight_retain` with `tier: "session-summary"` and structured content containing:
|
||||||
|
- Date, Task description, Repos worked on, Jira ticket key (or "none")
|
||||||
|
- Key Decisions, Outcomes, Next Steps
|
||||||
|
|
||||||
|
### Pitfalls (from RESEARCH.md)
|
||||||
|
Include all 6 pitfalls from the research: skill not loaded at start, epic cache too old, Confluence tag mismatch, Jira project 404, empty hindsight recall, long session eviction. Use the exact text from RESEARCH.md §Pitfalls.
|
||||||
|
|
||||||
|
### Verification (from RESEARCH.md)
|
||||||
|
Include the 6-point checklist matching the success criteria in must_haves.
|
||||||
|
|
||||||
|
**Formatting rules:**
|
||||||
|
- Code blocks inside Procedure MUST use triple-backtick with `bash` language tags for CLI commands, and plain text for hindsight tool calls
|
||||||
|
- Each numbered step should be a separate `### N. Title` heading
|
||||||
|
- Use bullet points for sub-steps, numbered lists for sequential actions
|
||||||
|
- The "Important" section must include the context-eviction reload note
|
||||||
|
- Do NOT include any deferred ideas (daily reporting, stale archive, auto-create Jira — these are Phase 8 and specifically deferred per DEF-01/02/03)
|
||||||
|
- The file must be >=180 lines to cover all decisions and procedures comprehensively
|
||||||
|
|
||||||
|
**Key content differences from the RESEARCH.md example (implement these corrections):**
|
||||||
|
1. Set `name: session` (not `ngn-agent-session`) in frontmatter — the directory name is `session/`, agent discovers as `ngn-agent/session`, so `skill_view("session")` is the expected call
|
||||||
|
2. Ensure D-08 is explicit: "Do NOT transition tickets (close/resolve) without explicit user confirmation" appears in Step 5
|
||||||
|
3. Add context-eviction reload reminder in the Important section and Pitfalls
|
||||||
|
4. Ensure the Step 7 hindsight_retain is clearly unconditional ("Do NOT ask the user — this step is automatic")
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<verify>
|
||||||
|
<automated>ls ~/.hermes/skills/ngn-agent/session/SKILL.md && wc -l ~/.hermes/skills/ngn-agent/session/SKILL.md</automated>
|
||||||
|
</verify>
|
||||||
|
|
||||||
|
<done>
|
||||||
|
- File exists at `~/.hermes/skills/ngn-agent/session/SKILL.md`
|
||||||
|
- File is >=180 lines
|
||||||
|
- Frontmatter contains all required fields (name, description, tags, category, requires_toolsets, version)
|
||||||
|
- All 7 procedure steps present with correct headings
|
||||||
|
- All sections present: When to Use, Important, Procedure, Pitfalls, Verification
|
||||||
|
- Step 2 references epic cache with hindsight_retain tier epic-cache
|
||||||
|
- Step 7 indicates automatic save (no user prompt)
|
||||||
|
- Step 8 and 9 do not exist (phase only has 7 steps)
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Verify skill discoverability, structure, and decision coverage</name>
|
||||||
|
|
||||||
|
<read_first>
|
||||||
|
- ~/.hermes/skills/ngn-agent/session/SKILL.md (just created in Task 1)
|
||||||
|
- 07-CONTEXT.md (decisions D-01 through D-14 for coverage check)
|
||||||
|
</read_first>
|
||||||
|
|
||||||
|
<files>
|
||||||
|
~/.hermes/skills/ngn-agent/session/SKILL.md
|
||||||
|
</files>
|
||||||
|
|
||||||
|
<action>
|
||||||
|
Run the following verification sequence:
|
||||||
|
|
||||||
|
1. **Discoverability:** Run `hermes skills list` and confirm the session skill appears in the output. If `hermes` CLI is not available, use `ls ~/.hermes/skills/ngn-agent/session/SKILL.md` to confirm file placement matches the pattern of 4 existing skills.
|
||||||
|
|
||||||
|
2. **Structure verification:** Read the created SKILL.md and confirm:
|
||||||
|
- YAML frontmatter is valid and contains: name, description, metadata.hermes.tags, metadata.hermes.category, metadata.hermes.requires_toolsets, version
|
||||||
|
- Sections present: ## When to Use, ## Important, ## Procedure, ## Pitfalls, ## Verification
|
||||||
|
- Procedure has exactly 7 numbered steps (### 1. through ### 7.)
|
||||||
|
- Step 7 heading says "Save to Hindsight" or similar — confirms automatic save
|
||||||
|
- All code blocks with CLI commands use ```bash language tag
|
||||||
|
|
||||||
|
3. **Decision coverage check:** grep the SKILL.md for each decision D-01 through D-14 to confirm it's implemented:
|
||||||
|
- D-01 → "hindsight_recall" in Step 1
|
||||||
|
- D-02 → "Would you like to create a Jira Task" in Step 2
|
||||||
|
- D-03 → "ngn-agent" tag search in Step 3
|
||||||
|
- D-04 → "Repos are already mounted" in Step 4
|
||||||
|
- D-05 → "issuetype.*Task" in Step 2 Jira POST
|
||||||
|
- D-06 → "Ask which Jira project" in Step 2
|
||||||
|
- D-07 → "epic cache" or "epic" in Step 2
|
||||||
|
- D-08 → "Do NOT transition" or "confirm" in Step 5
|
||||||
|
- D-09 → "tag.*ngn-agent" in Step 3
|
||||||
|
- D-10 → "which pages" or "user selects" in Step 3
|
||||||
|
- D-11 → "without asking" or "confirm" in Step 6
|
||||||
|
- D-12 → "no prompt" and "automatic" in Important or Step 7
|
||||||
|
- D-13 → structured summary fields (Date, Task, Repos, Jira, Decisions, Outcomes) in Step 7
|
||||||
|
- D-14 → frontmatter with name, description, tags, requires_toolsets
|
||||||
|
|
||||||
|
4. **Deferred ideas check:** grep for "daily report", "stale", "archive", "auto-create" — these MUST NOT appear in the skill content (they belong to Phase 8).
|
||||||
|
</action>
|
||||||
|
|
||||||
|
<verify>
|
||||||
|
<automated>grep -q '### 7\.' ~/.hermes/skills/ngn-agent/session/SKILL.md && grep -q 'hindsight_recall' ~/.hermes/skills/ngn-agent/session/SKILL.md && grep -q 'hindsight_retain' ~/.hermes/skills/ngn-agent/session/SKILL.md && grep -q 'ngn-jira' ~/.hermes/skills/ngn-agent/session/SKILL.md && grep -q 'ngn-confluence' ~/.hermes/skills/ngn-agent/session/SKILL.md</automated>
|
||||||
|
</verify>
|
||||||
|
|
||||||
|
<done>
|
||||||
|
- SKILL.md is discoverable by Hermes (appears in `hermes skills list` or confirmed in correct directory)
|
||||||
|
- All 14 decisions (D-01 through D-14) are implemented in the skill
|
||||||
|
- No deferred ideas (daily reporting, stale archive, auto-create) appear in the skill
|
||||||
|
- All 7 procedure steps are present and correctly ordered
|
||||||
|
- All 5 sections are present (When to Use, Important, Procedure, Pitfalls, Verification)
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Agent → Atlassian Cloud API | Jira/Confluence writes cross a network boundary to external SaaS. All mutations gated by user confirmation. |
|
||||||
|
| Agent → Hindsight local store | Session summary saves to local embedded PostgreSQL. Read-only recall unrestricted. |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-07-01 | Tampering | Jira ticket creation/update (ngn-jira POST) | mitigate | D-08: Procedure Step 5 explicitly instructs agent to ask user before any Jira mutation. Step 2 also requires user prompt before ticket creation. Skill language: "Never create Jira tickets without asking the user first" in Important section. |
|
||||||
|
| T-07-02 | Tampering | Confluence page create/update (ngn-confluence POST/PUT) | mitigate | D-11: Procedure Step 6 explicitly instructs agent to ask user before any Confluence update. Skill language: "Never update Confluence without asking the user first" in Important section. |
|
||||||
|
| T-07-03 | Information Disclosure | hindsight_retain session summary | mitigate | D-13: Summary format is structured, task-level information (repos, Jira ref, decisions, outcomes) — not raw conversation transcripts. Hindsight runs in local_embedded mode (local PostgreSQL). Skill does not instruct agent to retain raw conversation. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. File exists at `~/.hermes/skills/ngn-agent/session/SKILL.md` ✓
|
||||||
|
2. All 14 decisions (D-01 through D-14) are implemented — verified by grep in Task 2 ✓
|
||||||
|
3. No deferred ideas (daily reporting, stale archive, auto-create) are present ✓
|
||||||
|
4. Format matches the 4 existing ngn-agent skills (frontmatter, sections, conventions) ✓
|
||||||
|
5. Skill is discoverable by Hermes or confirmed at correct path ✓
|
||||||
|
6. All code blocks use correct syntax (bash for CLI, plain text for tool calls) ✓
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Agent can discover and load the session skill via `skill_view("session")`
|
||||||
|
- When loaded, the skill guides the agent through the full init→prompt→work→close lifecycle
|
||||||
|
- At session start: hindsight_recall check for similar sessions (D-01), user prompted for Jira (D-02) and Confluence (D-03)
|
||||||
|
- At session end: user prompted for Jira update (D-08) and Confluence update (D-11), session summary auto-saved to hindsight (D-12/D-13)
|
||||||
|
- No write operation (Jira/Confluence) happens without user confirmation
|
||||||
|
- Epic cache managed in hindsight with refresh when stale (D-07)
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/07-main-session-skill/07-01-SUMMARY.md` when done
|
||||||
|
</output>
|
||||||
144
.planning/phases/07-main-session-skill/07-01-SUMMARY.md
Normal file
144
.planning/phases/07-main-session-skill/07-01-SUMMARY.md
Normal file
@@ -0,0 +1,144 @@
|
|||||||
|
---
|
||||||
|
phase: 07-main-session-skill
|
||||||
|
plan: 01
|
||||||
|
subsystem: session-orchestration
|
||||||
|
tags: [hermes, skil.md, session, jira, confluence, hindsight]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 05-hindsight-memory-provider
|
||||||
|
provides: hindsight_recall, hindsight_retain tools for cross-session memory
|
||||||
|
- phase: 06-default-repos-ssh-mount
|
||||||
|
provides: DEFAULT_REPOS mounted at /workspace/, ngn-jira/ngn-confluence/ngn-bitbucket scripts
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- Session lifecycle orchestration SKILL.md (init→work→close pattern)
|
||||||
|
- Jira Task ticket creation procedure with epic cache management
|
||||||
|
- Confluence doc search and load by ngn-agent tag
|
||||||
|
- Automatic session summary save to hindsight
|
||||||
|
- Cross-session similarity detection via hindsight_recall
|
||||||
|
|
||||||
|
affects:
|
||||||
|
- Phase 8 cron reporting (daily reporting, stale archive deferred)
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- Hermes SKILL.md with 7-step procedural workflow
|
||||||
|
- Cross-tool orchestration (hindsight_recall → ngn-jira → ngn-confluence → hindsight_retain)
|
||||||
|
- User-confirmation gates before all Jira/Confluence mutations
|
||||||
|
- Epic cache management via hindsight_retain with tier: epic-cache
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- ~/.hermes/skills/ngn-agent/session/SKILL.md — Main session lifecycle skill (249 lines)
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Set name: session in frontmatter (not ngn-agent-session) for skill_view('session') discoverability"
|
||||||
|
- "Direct ngn-jira/ngn-confluence CLI commands in procedure instead of cross-skill_view loading"
|
||||||
|
- "Epic cache refresh strategy: refresh every 24h or on explicit user request"
|
||||||
|
- "Session summary uses structured plain text with fields: Date, Task, Repos, Jira, Key Decisions, Outcomes, Next Steps"
|
||||||
|
- "Deferred ideas (daily reporting, stale archive, auto-create) explicitly excluded from skill content"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Session lifecycle: hindsight_recall → Jira prompt → Confluence prompt → Work → Jira update → Confluence update → hindsight_retain"
|
||||||
|
- "All Jira/Confluence mutations gated behind user confirmation"
|
||||||
|
- "hindsight_retain uses tier field for memory categorization (epic-cache, session-summary)"
|
||||||
|
|
||||||
|
requirements-completed: [SKIL-04]
|
||||||
|
|
||||||
|
duration: 12min
|
||||||
|
completed: 2026-06-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 7 Plan 01: Main Session Skill Summary
|
||||||
|
|
||||||
|
**Hermes session SKILL.md at ~/.hermes/skills/ngn-agent/session/SKILL.md — 249-line session lifecycle covering init (hindsight recall), prompt (Jira/Confluence), work, close (Jira update, Confluence update, automatic hindsight save)**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 12 min
|
||||||
|
- **Started:** 2026-06-15T20:26:00Z
|
||||||
|
- **Completed:** 2026-06-15T20:38:00Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 1 (outside repo — system skill file at ~/.hermes/)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Created `session/SKILL.md` with 7-step procedure covering the full session lifecycle
|
||||||
|
- All 14 decisions (D-01 through D-14) implemented and verified
|
||||||
|
- Skill is discoverable via `hermes skills list` as `session` under `ngn-agent` category
|
||||||
|
- All 5 required sections present: When to Use, Important, Procedure, Pitfalls, Verification
|
||||||
|
- 6 pitfalls documented (skill not loaded, epic cache stale, Confluence tag mismatch, Jira project 404, empty recall, context eviction)
|
||||||
|
- Automatic session summary save to hindsight (Step 7) — no user prompt (D-12)
|
||||||
|
- All write operations gated behind user confirmation (D-08, D-11)
|
||||||
|
- No deferred ideas (daily reporting, stale archive, auto-create) appear in skill content
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was completed and verified. The skill file resides outside the project git repository (`~/.hermes/` is not a git repo), so no git commits were created for the individual tasks. The SUMMARY.md commit captures the plan completion metadata.
|
||||||
|
|
||||||
|
1. **Task 1: Create session/SKILL.md** — `~/.hermes/skills/ngn-agent/session/SKILL.md` created (249 lines, all sections present, all key content differences from RESEARCH.md example implemented)
|
||||||
|
2. **Task 2: Verify skill discoverability, structure, and decision coverage** — All 14 decisions D-01 through D-14 verified PASS, all 5 sections present, no deferred ideas found, discoverability confirmed via `hermes skills list`
|
||||||
|
|
||||||
|
**Plan metadata:** See final metadata commit for SUMMARY.md.
|
||||||
|
|
||||||
|
_Note: Skill file is outside git repo — tracked as external artifact._
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `~/.hermes/skills/ngn-agent/session/SKILL.md` — Main session lifecycle skill (249 lines, created)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **Skill name**: Set `name: session` in frontmatter (not `ngn-agent-session`) to match the directory name and enable `skill_view("session")` discovery. The RESEARCH.md example used `ngn-agent-session` but the plan specified `session` for cleaner discoverability.
|
||||||
|
- **Epic cache mechanism**: Epics cached in hindsight with `tier: "epic-cache"`. Cache refreshed when >24h old or on explicit user request. The procedure checks cache age from the cached content timestamp.
|
||||||
|
- **Hindsight tool calls**: Represented with plain text code blocks (not `bash` language tag) since they are Hermes tool calls, not CLI commands. CLI commands (ngn-jira, ngn-confluence) use `bash` language tag.
|
||||||
|
- **No cross-skill loading**: Session procedure uses direct `ngn-jira` and `ngn-confluence` CLI commands instead of calling `skill_view("jira-query")` or `skill_view("confluence-search")`. This avoids context replacement issues and keeps the procedure self-contained.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
### Key Content Corrections from RESEARCH.md Example
|
||||||
|
|
||||||
|
The plan explicitly noted 4 corrections to apply vs. the RESEARCH.md reference example, all implemented:
|
||||||
|
1. ✅ `name: session` (not `ngn-agent-session`) in frontmatter
|
||||||
|
2. ✅ D-08 explicit: "Do NOT transition tickets (close, resolve, move to Done) without explicit user confirmation" in Step 5
|
||||||
|
3. ✅ Context-eviction reload reminder in Important section ("reload via `skill_view("session")`") and Pitfalls
|
||||||
|
4. ✅ Step 7 hindsight_retain is clearly unconditional ("Do NOT ask the user — this step is automatic and unconditional")
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
- The skill file at `~/.hermes/skills/ngn-agent/session/SKILL.md` is outside the project git repository. No git commits possible for the individual task files. The file is a Hermes system skill and is not tracked in the ngn-agent project repo. This is expected — Hermes skills live under `~/.hermes/`, not in the project directory.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — all threat mitigations from the plan's threat model are implemented:
|
||||||
|
- T-07-01: Jira tampering mitigated via D-08 (user confirmation gates in Step 5, Important section)
|
||||||
|
- T-07-02: Confluence tampering mitigated via D-11 (user confirmation gates in Step 6, Important section)
|
||||||
|
- T-07-03: Information disclosure mitigated via D-13 (structured task-level summary, no raw conversation in hindsight_retain)
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Session skill fully ready for `skill_view("session")` loading
|
||||||
|
- Phase 8 (cron reporting, stale archive) can reference the session skill's `tier: "session-summary"` hindsight entries for daily reporting
|
||||||
|
- File is discoverable and enabled in Hermes — no additional configuration needed
|
||||||
|
- User can start any session and the agent will follow the init→prompt→work→close lifecycle when `skill_view("session")` is loaded
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
- Created file verified: `[ -f ~/.hermes/skills/ngn-agent/session/SKILL.md ]` → FOUND (249 lines)
|
||||||
|
- All 14 decisions (D-01 through D-14) verified via grep — ALL PASS
|
||||||
|
- All 5 sections present — PASS
|
||||||
|
- All 7 procedure steps present — PASS
|
||||||
|
- No deferred ideas present — PASS
|
||||||
|
- Skill discoverable via `hermes skills list` — PASS
|
||||||
|
- No threat flags — PASS
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 07-main-session-skill*
|
||||||
|
*Completed: 2026-06-15*
|
||||||
114
.planning/phases/07-main-session-skill/07-CONTEXT.md
Normal file
114
.planning/phases/07-main-session-skill/07-CONTEXT.md
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
# Phase 7: Main Session Skill - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-06-14
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
Create the main ngn-agent session orchestration SKILL.md — a Hermes skill that guides the agent through the session lifecycle: detect similar previous sessions, set up workspace, create Jira ticket, load Confluence docs, work, and update at session end.
|
||||||
|
|
||||||
|
**In scope:** Hermes SKILL.md file in `~/.hermes/skills/ngn-agent/`, session start/end workflow procedures, hindsight integration for similarity search and session saves, Jira ticket creation prompt, Confluence doc search by tag, on-demand epic cache
|
||||||
|
|
||||||
|
**Out of scope:** Cron reporting (Phase 8), stale session archive (Phase 8), on-demand repo cloning (already works from Phase 6), AWS diagnostics (existing skill), Bitbucket PR review (existing skill)
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### Session Start Behavior
|
||||||
|
- **D-01:** On session start, automatically check hindsight for similar sessions from the last 2 weeks. Present any matches to the user with option to resume or start fresh.
|
||||||
|
- **D-02:** Jira ticket creation is **always prompted** — create a Task type. The user decides whether to create one each session.
|
||||||
|
- **D-03:** Confluence doc loading is **always prompted** — when user wants docs, search by a dedicated ngn-agent tag.
|
||||||
|
- **D-04:** After prompting, load workspace (DEFAULT_REPOS already mounted via Phase 6), then proceed to work.
|
||||||
|
|
||||||
|
### Jira Integration
|
||||||
|
- **D-05:** Always create issue type `Task` for session tickets.
|
||||||
|
- **D-06:** Default Jira project/board is configurable — no hardcoded project. The agent asks which project when creating.
|
||||||
|
- **D-07:** Query available epics from Jira and cache them in hindsight long-term memory. Refresh the cache only occasionally or when the user explicitly asks. The agent should present cached epics when creating a ticket so the user can optionally set the parent epic.
|
||||||
|
- **D-08:** The user must confirm before any Jira update (comment, transition, etc.).
|
||||||
|
|
||||||
|
### Confluence Integration
|
||||||
|
- **D-09:** Create a dedicated tag for ngn-agent session documentation (e.g., `ngn-agent` or `platform-engineering`). All session-related Confluence pages use this tag.
|
||||||
|
- **D-10:** When the user wants to load docs, search Confluence by this tag and present matching pages. The user selects which to load.
|
||||||
|
- **D-11:** The user must confirm before any Confluence update.
|
||||||
|
|
||||||
|
### Session-End Behavior
|
||||||
|
- **D-12:** At session end (when user indicates work is complete or session wraps up):
|
||||||
|
- Prompt: "Update Jira?" — if yes, add summary comment
|
||||||
|
- Prompt: "Update Confluence?" — if yes, create/update doc
|
||||||
|
- **Always save session summary to hindsight memory** (no prompt — automatic)
|
||||||
|
- **D-13:** Session summary saved to hindsight includes: task description, repos worked on, Jira ticket reference (if created), key decisions, outcomes.
|
||||||
|
|
||||||
|
### Skill Format
|
||||||
|
- **D-14:** Hermes SKILL.md format — one file at `~/.hermes/skills/ngn-agent/session/SKILL.md` (following the existing skill convention). Standard frontmatter with `name`, `description`, `tags`, `requires_toolsets`.
|
||||||
|
|
||||||
|
### the agent's Discration
|
||||||
|
- **Tag name for Confluence**: `ngn-agent` is the default, but planner can pick something more specific if needed
|
||||||
|
- **Epic cache refresh strategy**: Refresh every 24h or on explicit user request — planner can define the exact mechanism
|
||||||
|
- **Similar session display format**: How many to show, what info to include (title, date, summary snippet)
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
**Downstream agents MUST read these before planning or implementing.**
|
||||||
|
|
||||||
|
### Existing Skills (Reference Format)
|
||||||
|
- `~/.hermes/skills/ngn-agent/aws-diagnostics/SKILL.md` — Reference for SKILL.md format (frontmatter, procedure, when to use)
|
||||||
|
- `~/.hermes/skills/ngn-agent/jira/SKILL.md` — Jira query skill (will be referenced by session skill)
|
||||||
|
- `~/.hermes/skills/ngn-agent/confluence/SKILL.md` — Confluence search skill (will be referenced by session skill)
|
||||||
|
|
||||||
|
### Completed Phases (Dependencies)
|
||||||
|
- `.planning/phases/05-hindsight-memory-provider/05-01-SUMMARY.md` — Hindsight active with `hindsight_recall`, `hindsight_retain`, `hindsight_reflect` tools
|
||||||
|
- `.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md` — DEFAULT_REPOS mounted at `/workspace/rai-*`, SSH keys active
|
||||||
|
|
||||||
|
### Project Documents
|
||||||
|
- `initial-plan.md` — Original session workflow specification (functions `session()`, `daily_report()`, `daily_cleanup()`)
|
||||||
|
- `.planning/REQUIREMENTS.md` §SKIL-04 — Requirement definition
|
||||||
|
- `.planning/ROADMAP.md` §Phase 7 — Phase goal and success criteria
|
||||||
|
|
||||||
|
### Hermes Configuration
|
||||||
|
- `~/.hermes/config.yaml` §`memory.provider: hindsight` — Active memory provider for recall/save
|
||||||
|
- `~/.hermes/.env` — JIRA_EMAIL, JIRA_API_TOKEN, DEFAULT_REPOS env vars
|
||||||
|
</canonical_refs>
|
||||||
|
|
||||||
|
<code_context>
|
||||||
|
## Existing Code Insights
|
||||||
|
|
||||||
|
### Reusable Assets
|
||||||
|
- **Existing skill format**: 4 SKILL.md files in `~/.hermes/skills/ngn-agent/` — use same frontmatter conventions, procedure format, and "When to Use" / "Pitfalls" sections
|
||||||
|
- **Hindsight tools**: `hindsight_recall` for similar session search, `hindsight_retain` for session summary save, `hindsight_reflect` for cross-session synthesis
|
||||||
|
- **Atlassian scripts**: `ngn-jira`, `ngn-confluence`, `ngn-bitbucket` at `~/.hermes/scripts/` mounted to `/usr/local/bin:ro` in Docker
|
||||||
|
|
||||||
|
### Established Patterns
|
||||||
|
- **SKILL.md structure**: name/description frontmatter → When to Use → Important → Procedure → Pitfalls → Verification
|
||||||
|
- **Script invocation**: Skills use `ngn-*` scripts via terminal commands inside Docker
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- `~/.hermes/skills/ngn-agent/session/SKILL.md` — New skill file (create)
|
||||||
|
- `~/.hermes/skills/ngn-agent/jira/SKILL.md` — Referenced for Jira operations
|
||||||
|
- `~/.hermes/skills/ngn-agent/confluence/SKILL.md` — Referenced for Confluence operations
|
||||||
|
- Hindsight memory — Used for similar session recall and session summary save
|
||||||
|
</code_context>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- The session skill should be the "entrypoint" skill that the agent loads when starting work on a platform engineering task
|
||||||
|
- Similar session check should be lightweight — quick hindsight_recall query, not a deep search
|
||||||
|
- The workflow mirrors `initial-plan.md`'s `func session()` — implement each step as a section in the skill procedure
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<deferred>
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Daily reporting cron (Phase 8)
|
||||||
|
- Stale session archive (Phase 8)
|
||||||
|
- Auto-create Jira tickets without prompt — deferred, user wants manual control
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 7-Main Session Skill*
|
||||||
|
*Context gathered: 2026-06-14*
|
||||||
54
.planning/phases/07-main-session-skill/07-DISCUSSION-LOG.md
Normal file
54
.planning/phases/07-main-session-skill/07-DISCUSSION-LOG.md
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
# Phase 7: Main Session Skill - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||||
|
> Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
|
||||||
|
|
||||||
|
**Date:** 2026-06-14
|
||||||
|
**Phase:** 7-Main Session Skill
|
||||||
|
**Areas discussed:** Session start behavior, Jira integration, Confluence docs, Session-end behavior
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Start Behavior
|
||||||
|
|
||||||
|
**User's choice:** Auto-check hindsight for similar sessions (<2 weeks). Jira ticket creation should prompt. Doc loading should prompt — docs need to search.
|
||||||
|
|
||||||
|
**Notes:** Three-tier behavior — similar sessions (auto), Jira (prompt), docs (prompt + search by tag).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jira Integration
|
||||||
|
|
||||||
|
**User's choice:** Always create Task type. Default board configurable later. Query available epics and cache in long-term memory — fetch occasionally or when asked.
|
||||||
|
|
||||||
|
**Notes:** Epic cache lives in hindsight memory. Don't fetch epics every session — use cached version, refresh on demand.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Confluence Doc Search
|
||||||
|
|
||||||
|
**User's choice:** Create a tag for ngn-agent. Search Confluence by that tag and do matching.
|
||||||
|
|
||||||
|
**Notes:** Tag name TBD (default: `ngn-agent`). Present matching pages, user selects which to load.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session-End Behavior
|
||||||
|
|
||||||
|
**User's choice:** Always ask before Jira/Confluence updates. Always save session to hindsight (no prompt).
|
||||||
|
|
||||||
|
**Notes:** Session save is automatic. Jira comment and Confluence update are prompted.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## the agent's Discretion
|
||||||
|
|
||||||
|
- Exact tag name for Confluence searches
|
||||||
|
- Epic cache refresh interval
|
||||||
|
- Similar session display format
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Daily reporting cron (Phase 8)
|
||||||
|
- Stale session archive (Phase 8)
|
||||||
|
- Auto-create Jira tickets (user wants manual control)
|
||||||
774
.planning/phases/07-main-session-skill/07-RESEARCH.md
Normal file
774
.planning/phases/07-main-session-skill/07-RESEARCH.md
Normal file
@@ -0,0 +1,774 @@
|
|||||||
|
# Phase 7: Main Session Skill — Research
|
||||||
|
|
||||||
|
**Researched:** 2026-06-15
|
||||||
|
**Domain:** Hermes Session Orchestration Skill (SKILL.md)
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Phase 7 creates the main ngn-agent session orchestration SKILL.md at `~/.hermes/skills/ngn-agent/session/SKILL.md` — a Hermes skill that guides the agent through the full session lifecycle defined in `initial-plan.md`: detect similar previous sessions, set up workspace, create Jira ticket, load Confluence docs, work, and update at session end.
|
||||||
|
|
||||||
|
**What this phase DOES NOT do:** It does not write any Python code, install any packages, modify any Hermes config, or change the Docker setup. It is purely a SKILL.md authoring phase — one markdown file with YAML frontmatter and structured procedure sections.
|
||||||
|
|
||||||
|
**How it works:** The session skill follows the standard Hermes progressive disclosure pattern. The agent loads it via `skill_view("ngn-agent/session")` when the "When to Use" conditions match (user starts a platform engineering task). The session skill's Procedure section then guides the agent through:
|
||||||
|
1. **Session Start**: Use `hindsight_recall` to find similar sessions → present to user
|
||||||
|
2. **Jira Prompt**: Ask user if they want a ticket → create Task via `ngn-jira` POST → optionally set epic from cached list
|
||||||
|
3. **Confluence Prompt**: Ask user if they want docs → search by tag via `ngn-confluence` GET
|
||||||
|
4. **(Work Phase)**: Repos already mounted (Phase 6), agent works normally
|
||||||
|
5. **Session End**: Prompt Jira update → prompt Confluence update → auto `hindsight_retain` session summary
|
||||||
|
|
||||||
|
**Cross-skill orchestration** is handled by the session skill instructing the agent to use `ngn-jira` and `ngn-confluence` CLI scripts directly (these are mounted into Docker). For skills already installed (jira-query, confluence-search), the session procedure tells the agent to load them via `skill_view("jira-query")` when needed.
|
||||||
|
|
||||||
|
**Primary recommendation:** Create a single `SKILL.md` following the exact same frontmatter and section conventions as the 4 existing ngn-agent skills (aws-diagnostics, bitbucket, jira, confluence). The skill structure is: YAML frontmatter → ## When to Use → ## Important → ## Procedure (numbered steps) → ## Pitfalls → ## Verification.
|
||||||
|
|
||||||
|
**Estimated effort:** ~30 minutes for SKILL.md creation and verification — one file, no installs, no config changes.
|
||||||
|
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
### Locked Decisions
|
||||||
|
- **D-01:** On session start, automatically check hindsight for similar sessions from the last 2 weeks. Present any matches to the user with option to resume or start fresh.
|
||||||
|
- **D-02:** Jira ticket creation is **always prompted** — create a Task type. The user decides whether to create one each session.
|
||||||
|
- **D-03:** Confluence doc loading is **always prompted** — when user wants docs, search by a dedicated ngn-agent tag.
|
||||||
|
- **D-04:** After prompting, load workspace (DEFAULT_REPOS already mounted via Phase 6), then proceed to work.
|
||||||
|
- **D-05:** Always create issue type `Task` for session tickets.
|
||||||
|
- **D-06:** Default Jira project/board is configurable — no hardcoded project. The agent asks which project when creating.
|
||||||
|
- **D-07:** Query available epics from Jira and cache them in hindsight long-term memory. Refresh the cache only occasionally or when the user explicitly asks. The agent should present cached epics when creating a ticket so the user can optionally set the parent epic.
|
||||||
|
- **D-08:** The user must confirm before any Jira update (comment, transition, etc.).
|
||||||
|
- **D-09:** Create a dedicated tag for ngn-agent session documentation (e.g., `ngn-agent` or `platform-engineering`). All session-related Confluence pages use this tag.
|
||||||
|
- **D-10:** When the user wants to load docs, search Confluence by this tag and present matching pages. The user selects which to load.
|
||||||
|
- **D-11:** The user must confirm before any Confluence update.
|
||||||
|
- **D-12:** At session end (when user indicates work is complete or session wraps up):
|
||||||
|
- Prompt: "Update Jira?" — if yes, add summary comment
|
||||||
|
- Prompt: "Update Confluence?" — if yes, create/update doc
|
||||||
|
- **Always save session summary to hindsight memory** (no prompt — automatic)
|
||||||
|
- **D-13:** Session summary saved to hindsight includes: task description, repos worked on, Jira ticket reference (if created), key decisions, outcomes.
|
||||||
|
- **D-14:** Hermes SKILL.md format — one file at `~/.hermes/skills/ngn-agent/session/SKILL.md` (following the existing skill convention). Standard frontmatter with `name`, `description`, `tags`, `requires_toolsets`.
|
||||||
|
|
||||||
|
### The agent's Discretion
|
||||||
|
- **Tag name for Confluence**: `ngn-agent` is the default, but planner can pick something more specific if needed
|
||||||
|
- **Epic cache refresh strategy**: Refresh every 24h or on explicit user request — planner can define the exact mechanism
|
||||||
|
- **Similar session display format**: How many to show, what info to include (title, date, summary snippet)
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
- Daily reporting cron (Phase 8)
|
||||||
|
- Stale session archive (Phase 8)
|
||||||
|
- Auto-create Jira tickets without prompt — deferred, user wants manual control
|
||||||
|
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|------------------|
|
||||||
|
| SKIL-04 | Main ngn-agent session orchestration skill covering the initial-plan.md workflow — detect similar prev sessions (via hindsight), load DEFAULT_REPOS, create Jira ticket per session, load Confluence docs, update docs/Jira at session end | All skill patterns, cross-skill orchestration, hindsight_recall/retain usage, and Jira/Confluence scripting documented below. Single-file SKILL.md at `~/.hermes/skills/ngn-agent/session/SKILL.md`. |
|
||||||
|
|
||||||
|
## Architectural Responsibility Map
|
||||||
|
|
||||||
|
| Capability | Primary Tier | Secondary Tier | Rationale |
|
||||||
|
|------------|-------------|----------------|-----------|
|
||||||
|
| Session workflow orchestration | **Hermes skill system** (SKILL.md) | — | Skill defines procedure; agent follows instructions. No plugin code needed. |
|
||||||
|
| Similar session detection | **Agent (LLM + hindsight_recall)** | — | Skill instructs agent to call `hindsight_recall` with task description query. Agent interprets results and presents to user. |
|
||||||
|
| Jira ticket creation | **Container (ngn-jira script)** | — | `ngn-jira` script mounted in Docker; skill provides the exact POST call. No Python plugin needed. |
|
||||||
|
| Confluence doc search | **Container (ngn-confluence script)** | — | `ngn-confluence` script mounted in Docker; skill provides the exact GET call. |
|
||||||
|
| Epic cache in hindsight | **Hindsight memory** | Agent (periodic refresh) | D-07: Epics queried from Jira, saved via `hindsight_retain` as long-term memory. Agent checks cache age and refreshes when stale. |
|
||||||
|
| Session summary persistence | **Hindsight memory** (hindsight_retain) | — | D-12: Auto-save at session end via `hindsight_retain` with structured summary. No prompt. |
|
||||||
|
| User prompting (Jira/Confluence) | **Agent (in-skill orchestration)** | — | The Procedure section instructs the agent to ask the user before any action. Standard skill pattern. |
|
||||||
|
| Cross-skill loading | **Agent (skill_view)** | — | Session skill tells agent to load jira-query and confluence-search skills when needed via `skill_view("jira-query")`. |
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core
|
||||||
|
No new libraries or packages. This phase creates one SKILL.md file:
|
||||||
|
|
||||||
|
| Component | Version | Purpose | Why Standard |
|
||||||
|
|-----------|---------|---------|--------------|
|
||||||
|
| Hermes SKILL.md format | — | Session lifecycle orchestration instructions | All 4 existing ngn-agent skills use this format. Hermes official docs define the structure. [VERIFIED: hermes docs + 4 existing skills] |
|
||||||
|
| `hindsight_recall` tool | — | Similar session detection on session start | Active via Phase 5 Hindsight setup in hybrid memory_mode. [VERIFIED: Phase 5 SUMMARY] |
|
||||||
|
| `hindsight_retain` tool | — | Save session summary at session end | Same source — Hindsight hybrid mode provides 3 tools. [VERIFIED: Phase 5 SUMMARY] |
|
||||||
|
| `ngn-jira` script | — | Jira ticket creation and update | Mounted at `/usr/local/bin/ngn-jira` inside Docker. [VERIFIED: existing skills + Phase 6 SUMMARY] |
|
||||||
|
| `ngn-confluence` script | — | Confluence search and page operations | Mounted at `/usr/local/bin/ngn-confluence` inside Docker. [VERIFIED: existing skills + Phase 6 SUMMARY] |
|
||||||
|
| `skill_view` tool | — | Load jira-query/confluence-search skills when needed | Standard Hermes progressive disclosure mechanism. [VERIFIED: Hermes docs §Skills System] |
|
||||||
|
|
||||||
|
### Supporting
|
||||||
|
| Component | Purpose | When to Use |
|
||||||
|
|-----------|---------|-------------|
|
||||||
|
| `skills_list()` | Agent discovers session and other skills | On session start, agent checks available skills |
|
||||||
|
| `hermes bundles create` | Group session + jira + confluence under one command | If user wants `/session` as a one-shot entry point |
|
||||||
|
| `skill_manage` tool | Agent-created skill updates | Not needed for Phase 7 — skill is hand-authored |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Instead of | Could Use | Tradeoff |
|
||||||
|
|------------|-----------|----------|
|
||||||
|
| Single session SKILL.md | Hermes plugin with `on_session_start` hook | Plugin requires Python code, plugin.yaml, and `register(ctx)` function. SKILL.md is simpler, zero code, no install. But plugin hooks guarantee execution on session start; skill depends on agent deciding to load it. |
|
||||||
|
| Skill bundle (session + jira + confluence) | Individual skill_view calls within session procedure | Bundle loads all skills at once, increasing token usage for skills that may not be needed. Sequential `skill_view` calls are more token-efficient. |
|
||||||
|
| Direct ngn-jira/ngn-confluence commands | Loading jira-query/confluence-search skills via skill_view | Both approaches work. Direct commands are simpler and more explicit. Loading skills via `skill_view` adds context about how to use the APIs including pitfalls. Prefer `skill_view` for richer context. |
|
||||||
|
|
||||||
|
**Installation:** No packages to install. One SKILL.md file created on disk.
|
||||||
|
|
||||||
|
## Package Legitimacy Audit
|
||||||
|
|
||||||
|
> No packages are installed in this phase. This is a single SKILL.md file creation — no npm, pip, or cargo dependencies.
|
||||||
|
|
||||||
|
| Package | Registry | Verdict | Disposition |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| — | — | — | No packages to verify |
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### System Architecture Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Hermes Agent (LLM) │
|
||||||
|
│ │
|
||||||
|
│ ┌──────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ skill_view("ngn-agent/session") │ │
|
||||||
|
│ │ ─────────────────────────────── │ │
|
||||||
|
│ │ Session SKILL.md loaded into context │ │
|
||||||
|
│ └────────────────────────────────┬─────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ┌───────────────────────┼───────────────────────┐ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ │
|
||||||
|
│ ┌─────────────────┐ ┌─────────────────┐ ┌──────────────────────┐ │
|
||||||
|
│ │ SKILL_VIEW │ │ HINDSIGHT_TOOLS │ │ JIRA/CONFLUENCE │ │
|
||||||
|
│ │ cross-skill │ │ recall/retain │ │ ngn-jira/ngn- │ │
|
||||||
|
│ │ loading │ │ │ │ confluence scripts │ │
|
||||||
|
│ │ │ │ │ │ │ │
|
||||||
|
│ │ skill_view( │ │ hindsight_recall│ │ ngn-jira GET/POST │ │
|
||||||
|
│ │ "jira-query") │ │ hindsight_retain│ │ ngn-confluence GET │ │
|
||||||
|
│ │ skill_view( │ │ │ │ ngn-confluence PUT │ │
|
||||||
|
│ │ "confluence- │ │ │ │ │ │
|
||||||
|
│ │ search") │ │ │ │ │ │
|
||||||
|
│ └─────────────────┘ └─────────────────┘ └──────────────────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
│ │ │
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────────┐ ┌──────────────────────┐ ┌──────────────────────┐
|
||||||
|
│ Existing Skills │ │ Hindsight Local │ │ Atlassian Cloud │
|
||||||
|
│ ~/.hermes/ │ │ Embedded Daemon │ │ (via Docker scripts) │
|
||||||
|
│ skills/ngn-agent/│ │ (PostgreSQL KG) │ │ │
|
||||||
|
│ │ │ │ │ Jira REST API │
|
||||||
|
│ jira-query │ │ Session KG: │ │ Confluence REST API │
|
||||||
|
│ confluence- │ │ - similar sessions │ │ │
|
||||||
|
│ search │ │ - epic cache │ │ │
|
||||||
|
│ │ │ - session summaries │ │ │
|
||||||
|
│ aws-diagnostics │ │ │ │ │
|
||||||
|
│ bitbucket-pr │ │ │ │ │
|
||||||
|
└─────────────────┘ └──────────────────────┘ └──────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
**Data flow through the session lifecycle:**
|
||||||
|
|
||||||
|
```
|
||||||
|
User: "I need to work on the CI/CD pipeline"
|
||||||
|
|
||||||
|
1. Agent loads session skill via skill_view("ngn-agent/session")
|
||||||
|
→ Session SKILL.md instructions enter context
|
||||||
|
|
||||||
|
2. [SESSION START] hindsight_recall(query="CI/CD pipeline work")
|
||||||
|
→ Returns similar sessions from last 2 weeks
|
||||||
|
→ Agent presents to user: "Found session 'Pipeline fixes' from 2 days ago — resume or start fresh?"
|
||||||
|
|
||||||
|
3. [JIRA PROMPT] Agent asks: "Create a Jira Task ticket for this session?"
|
||||||
|
→ If yes: agent asks for project, shows cached epics (from hindsight), creates via ngn-jira POST
|
||||||
|
→ If no: continues without ticket
|
||||||
|
|
||||||
|
4. [CONFLUENCE PROMPT] Agent asks: "Load Confluence docs tagged 'ngn-agent'?"
|
||||||
|
→ If yes: ngn-confluence GET search by tag, present pages, user selects
|
||||||
|
→ If no: continues without docs
|
||||||
|
|
||||||
|
5. [WORK PHASE] Agent works on task using mounted repos (Phase 6)
|
||||||
|
→ Normal Hermes operation
|
||||||
|
→ (Session skill stays loaded for end-of-session steps)
|
||||||
|
|
||||||
|
6. [SESSION END] User says "I'm done" or session wraps up
|
||||||
|
→ Agent: "Update Jira with a summary comment?"
|
||||||
|
→ Agent: "Update Confluence documentation?"
|
||||||
|
→ AUTO: hindsight_retain(structured summary)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.hermes/skills/ngn-agent/
|
||||||
|
├── aws-diagnostics/
|
||||||
|
│ └── SKILL.md (existing — unchanged)
|
||||||
|
├── bitbucket/
|
||||||
|
│ └── SKILL.md (existing — unchanged)
|
||||||
|
├── confluence/
|
||||||
|
│ └── SKILL.md (existing — unchanged)
|
||||||
|
├── jira/
|
||||||
|
│ └── SKILL.md (existing — unchanged)
|
||||||
|
└── session/
|
||||||
|
└── SKILL.md ★ NEW — main session orchestration skill
|
||||||
|
```
|
||||||
|
|
||||||
|
Only the `session/SKILL.md` file is new. Everything else already exists from v1.0.
|
||||||
|
|
||||||
|
### Pattern 1: Hermes SKILL.md Format (from 4 existing ngn-agent skills)
|
||||||
|
|
||||||
|
**What:** All 4 existing ngn-agent skills follow an identical structure. The session skill MUST use the same format.
|
||||||
|
|
||||||
|
**When to use:** Every custom skill in the ngn-agent ecosystem.
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: <hyphenated-name>
|
||||||
|
description: <one-line description>
|
||||||
|
metadata:
|
||||||
|
hermes:
|
||||||
|
tags: [<keywords>]
|
||||||
|
category: devops
|
||||||
|
requires_toolsets: [terminal]
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
# <Skill Title>
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
<conditions that trigger loading this skill>
|
||||||
|
|
||||||
|
## Important
|
||||||
|
<key rules, non-obvious constraints, safety notes>
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
### 1. <Step title>
|
||||||
|
<instructions, command templates, decision points>
|
||||||
|
|
||||||
|
### 2. <Step title>
|
||||||
|
<instructions>
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
- <known failure modes and how to avoid them>
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
<how to confirm the skill executed correctly>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: 4 existing ngn-agent SKILL.md files at `~/.hermes/skills/ngn-agent/*/SKILL.md` — all follow identical structure.]
|
||||||
|
[VERIFIED: Hermes official docs §Skills System — confirms SKILL.md format with frontmatter fields.]
|
||||||
|
|
||||||
|
### Pattern 2: Cross-Skill Orchestration within a Procedure
|
||||||
|
|
||||||
|
**What:** A skill's procedure can instruct the agent to load another skill, use a CLI script directly, or use a Hermes tool. The session skill uses all three patterns.
|
||||||
|
|
||||||
|
**When to use:** When one skill's workflow depends on capabilities provided by other skills (e.g., session workflow needs Jira ticket creation and Confluence search).
|
||||||
|
|
||||||
|
**Pattern 2a — Load another skill via skill_view:**
|
||||||
|
The Procedure says: "Load the jira-query skill by calling `skill_view("jira-query")`." The agent then loads the full Jira skill content into context for detailed API guidance. This is useful for complex operations with multiple API endpoints.
|
||||||
|
|
||||||
|
**Pattern 2b — Direct CLI invocation:**
|
||||||
|
The Procedure provides the exact command to run, referencing scripts the agent has access to in Docker. This is simpler and more explicit for well-known operations.
|
||||||
|
|
||||||
|
```
|
||||||
|
# Direct approach (more explicit):
|
||||||
|
ngn-jira POST '/rest/api/3/issue' --body '{...}'
|
||||||
|
|
||||||
|
# Skill-loading approach (more context):
|
||||||
|
skill_view("jira-query")
|
||||||
|
# Then use the commands from that skill
|
||||||
|
```
|
||||||
|
|
||||||
|
**Recommendation:** Use direct CLI commands for well-known operations (create ticket = POST to `/rest/api/3/issue`). Use `skill_view` for exploratory or multi-endpoint operations where the agent needs full API context.
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: Hermes docs §Skills System — skill_view is the standard tool for loading skill content.]
|
||||||
|
[VERIFIED: 4 existing ngn-agent skills all use direct CLI commands for their procedures — no cross-skill references among them (they're independent). The session skill is the first to need orchestration.]
|
||||||
|
|
||||||
|
### Pattern 3: Skill-Borne User Prompts
|
||||||
|
|
||||||
|
**What:** A Hermes skill's Procedure section can instruct the agent to ask the user questions interactively. This is a standard pattern — the agent follows the skill's instructions and, at decision points, messages the user for input.
|
||||||
|
|
||||||
|
**When to use:** Any skill that requires user decisions at runtime (e.g., "which project?", "which epic?", "create Jira ticket?").
|
||||||
|
|
||||||
|
**How it looks in the procedure:**
|
||||||
|
```
|
||||||
|
### 2. Create Jira Ticket (Prompt)
|
||||||
|
Ask the user: "Would you like to create a Jira Task ticket for this session?"
|
||||||
|
- If YES: ...
|
||||||
|
- If NO: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: This is how standard Hermes skills work — the agent follows the procedure literally, including asking questions. The 4 existing ngn-agent skills don't have user prompts (they're query-only), but this is standard LLM tool-use behavior.]
|
||||||
|
|
||||||
|
### Pattern 4: Hindsight Similarity Search via `hindsight_recall`
|
||||||
|
|
||||||
|
**What:** The session skill instructs the agent to call `hindsight_recall` with a query describing the current task. Hindsight returns relevant memories from past sessions. The agent presents these to the user.
|
||||||
|
|
||||||
|
**When to use:** At session start (D-01) — check for similar sessions from the last 2 weeks.
|
||||||
|
|
||||||
|
```
|
||||||
|
# In the procedure:
|
||||||
|
### 1. Check for Similar Sessions
|
||||||
|
Use the `hindsight_recall` tool with a query describing the current task
|
||||||
|
to find similar sessions from the last 2 weeks.
|
||||||
|
|
||||||
|
Parse the results for session references. Present to the user in this format:
|
||||||
|
"Found X similar sessions from the last 2 weeks:"
|
||||||
|
- [Session Title] — [Date] — [Summary snippet]
|
||||||
|
Ask: "Would you like to resume any of these or start fresh?"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key insight:** Hindsight's `recall_types: observation` (set in Phase 5) means recall returns consolidated knowledge, not raw conversation turns. Session summaries saved via `hindsight_retain` with structured fields will be returned as observations, making them easy to identify as "previous sessions."
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: Phase 5 hindsight config has `memory_mode: hybrid` — all 3 hindsight tools available. `hindsight_recall` queries the KG for semantically similar content.]
|
||||||
|
|
||||||
|
### Pattern 5: Session Summary via `hindsight_retain`
|
||||||
|
|
||||||
|
**What:** At session end (D-12), the skill instructs the agent to call `hindsight_retain` with a structured summary containing task description, repos worked on, Jira ticket reference, key decisions, and outcomes.
|
||||||
|
|
||||||
|
**When to use:** On every session end — automatic, no prompt (D-12).
|
||||||
|
|
||||||
|
**Recommended format for the summary:**
|
||||||
|
```
|
||||||
|
hindsight_retain(tier="session-summary", content="
|
||||||
|
Session Summary
|
||||||
|
===============
|
||||||
|
Date: 2026-06-15
|
||||||
|
Task: Fix broken CI/CD pipeline for rai-deployment
|
||||||
|
Repos: rai-deployment, rai-ops
|
||||||
|
Jira: PLATFORM-123
|
||||||
|
Key Decisions:
|
||||||
|
- Switched CI runner from GitHub Actions to self-hosted
|
||||||
|
- Added timeout of 30 min for deployment jobs
|
||||||
|
Outcomes:
|
||||||
|
- CI/CD pipeline fixed and verified with 3 test runs
|
||||||
|
- Dockerfile cache layer optimized (build time reduced from 8m to 3m)
|
||||||
|
")
|
||||||
|
```
|
||||||
|
|
||||||
|
**Format considerations:** The summary should be structured text (not JSON) — Hindsight processes natural language observations best. Include all 5 fields from D-13. The `tier="session-summary"` helps differentiate from other memory types during recall queries.
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: Phase 5 SUMMARY confirms `hindsight_retain` tool is available in hybrid mode. D-12 and D-13 define the save behavior and content.]
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- **Hardcoding Jira project name:** D-06 says configurable. The skill should instruct the agent to ASK the user which project, not hardcode.
|
||||||
|
- **Loading all skills upfront (bundle approach):** Loading jira-query AND confluence-search AND session at the same start wastes tokens. Load them sequentially only when needed.
|
||||||
|
- **Auto-creating Jira tickets without prompt:** D-02 says always prompt. The skill must explicitly instruct the agent to ask first.
|
||||||
|
- **Forgetting to mention the `ngn-agent` Confluence tag:** D-09 creates a dedicated tag. The skill must reference this tag in the Confluence search step.
|
||||||
|
- **Skipping the hindsight save:** D-12 specifies auto-save (no prompt). The skill must NOT have a user prompt for the save step — it should be unconditional.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| Session workflow execution | Custom Python plugin with hooks | Hermes SKILL.md | SKILL.md is declarative, zero-code, follows progressive disclosure. Agent loads it naturally via `skill_view()`. |
|
||||||
|
| Cross-skill orchestration | Custom tool that combines skills | Skill procedure with `skill_view()` calls | Agent already has `skill_view` tool. Telling the agent to load another skill from within a procedure is the standard pattern. |
|
||||||
|
| User prompting system | Custom input collection mechanism | Inline agent questions | The agent naturally asks users questions as part of conversation. The skill just says "Ask the user..." and the agent does it. |
|
||||||
|
| Session similarity detection | Custom Python vector search | `hindsight_recall` tool | Hindsight already has semantic search with KG, entity resolution, and cross-session recall. Using it within the skill is zero additional code. |
|
||||||
|
|
||||||
|
**Key insight:** The session skill is pure instruction — it doesn't need any code. The agent already has all the tools it needs (`hindsight_recall`, `hindsight_retain`, `ngn-jira`, `ngn-confluence`, `skill_view`). The SKILL.md just tells the agent when and how to use them in sequence.
|
||||||
|
|
||||||
|
## Runtime State Inventory
|
||||||
|
|
||||||
|
> Not applicable — this is a greenfield SKILL.md creation phase. No existing runtime state references old names, keys, or paths.
|
||||||
|
|
||||||
|
| Category | Items Found | Action Required |
|
||||||
|
|----------|-------------|------------------|
|
||||||
|
| Stored data | None | — |
|
||||||
|
| Live service config | None | — |
|
||||||
|
| OS-registered state | None | — |
|
||||||
|
| Secrets/env vars | None | — |
|
||||||
|
| Build artifacts | None | — |
|
||||||
|
|
||||||
|
**Nothing found:** This phase creates one new file (`session/SKILL.md`). No rename, refactor, or migration involved.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Session Skill Not Auto-Loaded by Agent
|
||||||
|
**What goes wrong:** The agent starts a platform engineering task without loading the session skill, so the session workflow (hindsight check, Jira prompt, Confluence prompt) is skipped entirely.
|
||||||
|
|
||||||
|
**Why it happens:** Skills follow progressive disclosure. The agent uses `skills_list()` to see all available skills, then decides which to load via `skill_view()`. If the agent doesn't recognize the "When to Use" conditions apply, it won't load the skill. Unlike plugin hooks (which fire automatically), skills require the agent's judgment.
|
||||||
|
|
||||||
|
**How to avoid:**
|
||||||
|
- Make the "When to Use" section broad and explicit: "Load this skill at the START of EVERY platform engineering session, before any other work. This skill defines the standard session workflow."
|
||||||
|
- Include trigger examples in "When to Use": "When the user says they want to work on infrastructure, or when you start a new platform engineering task, or when you need to create a Jira ticket or find Confluence docs."
|
||||||
|
- Consider adding the skill name in the Hermes config's `skills.always_include` if such a setting exists, or instruct the user to start sessions with `/session` to force-load it.
|
||||||
|
|
||||||
|
**Warning signs:** Agent starts working on repos directly without asking about Jira tickets or Confluence docs. No hindsight check at start.
|
||||||
|
|
||||||
|
### Pitfall 2: Agent Doesn't Load Dependent Skills When Instructed
|
||||||
|
**What goes wrong:** The session procedure says "Load the jira-query skill" but the agent either ignores the instruction or tries to use tools it doesn't have.
|
||||||
|
|
||||||
|
**Why it happens:** The agent may have a loaded stale skill list. Or the agent might try to use a tool (like `ngn-jira` POST) without first loading the skill that documents the API.
|
||||||
|
|
||||||
|
**How to avoid:** Include the exact tool call in the procedure: "Call `skill_view("jira-query")` to load the Jira skill. Then use the `ngn-jira` command documented there." Be explicit about the skill name — the agent will try to match it with a skill name from `skills_list()`.
|
||||||
|
|
||||||
|
**Warning signs:** Agent says "I don't have access to a Jira tool" or tries to use an incorrect command.
|
||||||
|
|
||||||
|
### Pitfall 3: Epic Cache Becomes Stale
|
||||||
|
**What goes wrong:** The epic list cached in hindsight shows old/closed epics, and the agent doesn't realize it's stale.
|
||||||
|
|
||||||
|
**Why it happens:** D-07 says "refresh only occasionally." Without a clear refresh strategy, the cache stays stale indefinitely.
|
||||||
|
|
||||||
|
**How to avoid:** The Procedure should include a step: "Check the age of the cached epic list. If it's more than 24 hours old, or if the user says the list looks wrong, query Jira for current epics and update the cache via `hindsight_retain` with a fresh list."
|
||||||
|
|
||||||
|
**Warning signs:** User says "that epic was closed last month" or "I don't see the current sprint's epics."
|
||||||
|
|
||||||
|
### Pitfall 4: Hindsight_retain Session Summary Conflicts with Auto-Retain
|
||||||
|
**What goes wrong:** Hindsight's `auto_retain: true` (Phase 5, D-10) already saves conversation turns every 5 turns. The session summary saved via `hindsight_retain` at session end may duplicate information or conflict with auto-saved observations.
|
||||||
|
|
||||||
|
**Why it happens:** Two retention mechanisms operate independently: auto_retain creates observations from conversation turns; the skill instructs the agent to manually retain a structured summary at session end.
|
||||||
|
|
||||||
|
**How to avoid:** Design the session summary to be a higher-level, consolidated view — not a duplicate of individual observations. Include data that auto-retain wouldn't capture (Jira ticket number, key decisions summary, outcome assessment). Use a distinct `tier="session-summary"` to differentiate from auto-saved observations.
|
||||||
|
|
||||||
|
**Warning signs:** hindsight_recall returns very similar results from both session summaries and observations.
|
||||||
|
|
||||||
|
### Pitfall 5: Skill Not Re-loaded for Session-End Steps
|
||||||
|
**What goes wrong:** The session-end steps (Jira/Confluence update prompts, hindsight_retain) are skipped because the agent no longer has the session skill loaded in context.
|
||||||
|
|
||||||
|
**Why it happens:** Skills are loaded once. If the session is long (many conversation turns), the skill content may be evicted from context by newer messages. The agent can't follow instructions it no longer has.
|
||||||
|
|
||||||
|
**How to avoid:** Include a note in the "Important" section: "Keep this skill loaded for the entire session. If context becomes too large, re-load this skill with `skill_view("ngn-agent/session")` before the session-end steps." Or make the session-end steps prominent enough that the agent checks back.
|
||||||
|
|
||||||
|
**Warning signs:** Agent completes work and waits for next instruction without offering to update Jira/Confluence or save to hindsight.
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### Example 1: session/SKILL.md (Full Content)
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: ngn-agent-session
|
||||||
|
description: Main ngn-agent session lifecycle — init, work, close
|
||||||
|
metadata:
|
||||||
|
hermes:
|
||||||
|
tags: [ngn-agent, platform-engineering, session]
|
||||||
|
category: devops
|
||||||
|
requires_toolsets: [terminal]
|
||||||
|
version: 1.0.0
|
||||||
|
---
|
||||||
|
# ngn-agent Session Lifecycle
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
When the user starts any platform engineering task or indicates they want to begin work. Load this skill at the START of every session before any other work. This is the standard session workflow for ngn-agent.
|
||||||
|
|
||||||
|
Also use when ending a session — the session-end steps ensure work is documented and saved.
|
||||||
|
|
||||||
|
## Important
|
||||||
|
- **Keep this skill loaded for the entire session** — if context grows large, reload via `skill_view("ngn-agent-session")`
|
||||||
|
- Never create Jira tickets without asking the user first
|
||||||
|
- Never update Confluence without asking the user first
|
||||||
|
- Always save session summary to hindsight at end (no user prompt for this step)
|
||||||
|
- Repos are already mounted at `/workspace/` from session start (Phase 6)
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
### 1. Check for Similar Previous Sessions
|
||||||
|
At the very start of a session, use `hindsight_recall` with a query describing the user's current task to find similar sessions from the last 2 weeks.
|
||||||
|
|
||||||
|
Example hindsight_recall call:
|
||||||
|
```
|
||||||
|
Tool: hindsight_recall
|
||||||
|
Query: "<user's task description>"
|
||||||
|
Budget: low
|
||||||
|
```
|
||||||
|
|
||||||
|
Present any matches to the user in this format:
|
||||||
|
```
|
||||||
|
Found [N] similar sessions from the last 2 weeks:
|
||||||
|
1. [Session Title] — [Date] — [one-line summary]
|
||||||
|
2. [Session Title] — [Date] — [one-line summary]
|
||||||
|
```
|
||||||
|
|
||||||
|
Ask the user: "Would you like to resume any of these sessions, or start fresh?"
|
||||||
|
- If they choose to resume: load that session's context and continue
|
||||||
|
- If they choose fresh: proceed to step 2
|
||||||
|
|
||||||
|
### 2. Prompt: Create Jira Ticket
|
||||||
|
Ask the user: "Would you like to create a Jira Task ticket for this session?"
|
||||||
|
- If YES:
|
||||||
|
1. Ask which Jira project to use (e.g., "PLATFORM", "DEVOPS")
|
||||||
|
2. Check hindsight for cached epics:
|
||||||
|
```
|
||||||
|
Tool: hindsight_recall
|
||||||
|
Query: "jira epics cached"
|
||||||
|
```
|
||||||
|
3. Present cached epics to the user: "Available epics: [list]. Would you like to set a parent epic?"
|
||||||
|
- If the cache is >24h old OR user says it looks wrong, refresh:
|
||||||
|
```
|
||||||
|
ngn-jira GET '/rest/api/3/search?jql=issuetype=Epic AND project=<PROJECT>&fields=summary,id&maxResults=50'
|
||||||
|
```
|
||||||
|
Save fresh epics to hindsight:
|
||||||
|
```
|
||||||
|
Tool: hindsight_retain
|
||||||
|
tier: "epic-cache"
|
||||||
|
content: "Jira Epic Cache [date]: [project]: [list of epic key + summary]"
|
||||||
|
```
|
||||||
|
- If epic cache is fresh (less than 24h), use the cached list
|
||||||
|
4. Create the Task:
|
||||||
|
```
|
||||||
|
ngn-jira POST '/rest/api/3/issue' --body '{
|
||||||
|
"fields": {
|
||||||
|
"project": {"key": "<PROJECT>"},
|
||||||
|
"summary": "<session task description>",
|
||||||
|
"issuetype": {"name": "Task"},
|
||||||
|
"parent": {"key": "<EPIC_KEY>"} // optional, if user selected an epic
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
5. Save the ticket key (e.g., `PLATFORM-123`) for use in session-end steps
|
||||||
|
- If NO: proceed to step 3 (no Jira ticket this session)
|
||||||
|
|
||||||
|
### 3. Prompt: Load Confluence Documentation
|
||||||
|
Ask the user: "Would you like to load relevant Confluence documentation?"
|
||||||
|
- If YES:
|
||||||
|
1. Search by the ngn-agent tag:
|
||||||
|
```
|
||||||
|
ngn-confluence GET '/rest/api/search?cql=tag="ngn-agent"&limit=20'
|
||||||
|
```
|
||||||
|
2. Present matching pages to the user: "Found [N] pages tagged 'ngn-agent':"
|
||||||
|
- [Title] — [space] — [last modified date]
|
||||||
|
3. Ask: "Which pages would you like me to load?"
|
||||||
|
4. For each selected page, load its content:
|
||||||
|
```
|
||||||
|
ngn-confluence GET '/rest/api/content/{pageId}?expand=body.storage'
|
||||||
|
```
|
||||||
|
- If NO: proceed to step 4
|
||||||
|
|
||||||
|
### 4. Work Phase
|
||||||
|
Repos are already mounted at `/workspace/` (rai-ops, rai-deployment, rai-devtools). Proceed with the task using standard Hermes tools.
|
||||||
|
|
||||||
|
If you need to clone additional repos:
|
||||||
|
```
|
||||||
|
git clone git@bitbucket.org:razersw/<repo>.git /workspace/<repo>
|
||||||
|
```
|
||||||
|
|
||||||
|
The session skill remains loaded for the session-end steps below.
|
||||||
|
|
||||||
|
### 5. Session-End: Update Jira
|
||||||
|
When the user indicates work is complete or the session wraps up:
|
||||||
|
Ask the user: "Would you like me to update the Jira ticket with a summary comment?"
|
||||||
|
- If YES (and a ticket was created in step 2):
|
||||||
|
```
|
||||||
|
ngn-jira POST '/rest/api/3/issue/<TICKET-KEY>/comment' --body '{
|
||||||
|
"body": "<summary of work done, key decisions, next steps>"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
- If NO: proceed without updating Jira
|
||||||
|
|
||||||
|
Note: Do NOT transition tickets (e.g., close/resolve) without explicit user confirmation (D-08).
|
||||||
|
|
||||||
|
### 6. Session-End: Update Confluence
|
||||||
|
Ask the user: "Would you like me to create or update a Confluence page documenting this session?"
|
||||||
|
- If YES:
|
||||||
|
- If this is a new page:
|
||||||
|
```
|
||||||
|
ngn-confluence POST '/rest/api/content' --body '{
|
||||||
|
"type": "page",
|
||||||
|
"title": "<Session Date>: <Task Description>",
|
||||||
|
"space": {"key": "<SPACE_KEY>"},
|
||||||
|
"body": {
|
||||||
|
"storage": {
|
||||||
|
"value": "<h1>Session Summary</h1><p>...</p>",
|
||||||
|
"representation": "storage"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"properties": {
|
||||||
|
"content-appearance": {"value": "page"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"labels": [{"name": "ngn-agent"}]
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
- If updating an existing page: ask which page, then PUT to update
|
||||||
|
- If NO: proceed without updating Confluence
|
||||||
|
|
||||||
|
### 7. Session-End: Save to Hindsight (Automatic — No Prompt)
|
||||||
|
ALWAYS save a session summary to hindsight memory. Do NOT ask the user — this step is automatic.
|
||||||
|
|
||||||
|
```
|
||||||
|
Tool: hindsight_retain
|
||||||
|
tier: "session-summary"
|
||||||
|
content: "
|
||||||
|
Session Summary
|
||||||
|
===============
|
||||||
|
Date: <today>
|
||||||
|
Task: <task description>
|
||||||
|
Repos: <repos worked on>
|
||||||
|
Jira: <ticket key or "none">
|
||||||
|
Key Decisions:
|
||||||
|
- <decision 1>
|
||||||
|
- <decision 2>
|
||||||
|
Outcomes:
|
||||||
|
- <outcome 1>
|
||||||
|
- <outcome 2>
|
||||||
|
Next Steps:
|
||||||
|
- <next step 1>
|
||||||
|
"
|
||||||
|
```
|
||||||
|
|
||||||
|
This summary allows future `hindsight_recall` queries to find this session for similarity matching.
|
||||||
|
|
||||||
|
## Pitfalls
|
||||||
|
|
||||||
|
- **Skill not loaded at session start:** If you reach step 2 without doing step 1 (hindsight check), execute step 1 retroactively or ask the user if they want to check for similar sessions now
|
||||||
|
- **Epic cache too old:** Epics may change between sessions. Check the cache timestamp and refresh if >24h old
|
||||||
|
- **Confluence tag mismatch:** If the `ngn-agent` tag returns no results, try `platform-engineering` as fallback, or ask the user what tag they use
|
||||||
|
- **Jira project doesn't exist:** If the create ticket call fails with 404, the project key may be wrong — ask the user to confirm
|
||||||
|
- **Hindsight recall returns nothing:** The first few sessions may have no matches. That's normal — proceed with fresh session
|
||||||
|
- **Long sessions may evict this skill:** If context becomes large, reload with `skill_view("ngn-agent-session")` before steps 5-7
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
1. On session start, agent checks for similar sessions ✓
|
||||||
|
2. Jira ticket created (or user declined) ✓
|
||||||
|
3. Confluence docs loaded (or user declined) ✓
|
||||||
|
4. At session end, user prompted for Jira/Confluence updates ✓
|
||||||
|
5. Session summary saved to hindsight ✓
|
||||||
|
6. Future sessions can recall this session via hindsight_recall ✓
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The skill above uses `ngn-agent-session` as the name field. The actual skill name should match what's used in the directory. Since the skill will live at `~/.hermes/skills/ngn-agent/session/SKILL.md`, the agent might discover it as `ngn-agent/session` (directory path) or as the `name` field. Use `name: ngn-agent-session` for a clean identifier.
|
||||||
|
|
||||||
|
**Source:** [VERIFIED: Existing ngn-agent skills use the same SKILL.md structure. Hermes official docs confirm all frontmatter fields and section conventions.]
|
||||||
|
|
||||||
|
### Example 2: Cross-Skill Loading from Procedure
|
||||||
|
|
||||||
|
When the session procedure wants the agent to use the Jira skill's detailed guidance for a complex operation:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 2a. Create Jira Ticket
|
||||||
|
Before creating the ticket, load the jira-query skill for detailed API reference:
|
||||||
|
1. Call `skill_view("jira-query")`
|
||||||
|
2. Review the procedure for creating issues
|
||||||
|
3. Use `ngn-jira POST '/rest/api/3/issue'` with the correct field structure
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent loads the full jira-query skill content into context, gaining access to its procedure, pitfalls, and environment variable requirements.
|
||||||
|
|
||||||
|
### Example 3: Hindsight Epic Cache Management
|
||||||
|
|
||||||
|
Pattern for caching and refreshing epics:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
### 2b. Epic Selection (if user wants to set parent)
|
||||||
|
1. Call `hindsight_recall` with query "epic cache" and budget "low"
|
||||||
|
2. If results contain a cached epic list:
|
||||||
|
- Parse the cache timestamp from the content
|
||||||
|
- If timestamp is >24h old, refresh: query Jira for current epics and `hindsight_retain` the updated list
|
||||||
|
- If fresh, present the cached epics to the user
|
||||||
|
3. If no cached epics found:
|
||||||
|
- Query Jira:
|
||||||
|
```bash
|
||||||
|
ngn-jira GET '/rest/api/3/search?jql=issuetype=Epic AND project=<PROJECT>&fields=summary,id&maxResults=50'
|
||||||
|
```
|
||||||
|
- Save to hindsight:
|
||||||
|
```
|
||||||
|
Tool: hindsight_retain
|
||||||
|
tier: "epic-cache"
|
||||||
|
content: "Epic Cache [2026-06-15]: PROJECT=PLATFORM: [PLATFORM-10: CI/CD Pipeline, PLATFORM-11: Infrastructure Migration, PLATFORM-12: Security Hardening]"
|
||||||
|
```
|
||||||
|
```
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| (v1.0) No session workflow — ad-hoc per session | SKILL.md with structured session lifecycle | This phase | Every session follows the same init→work→close pattern. Consistent, documented, auditable. |
|
||||||
|
| (v1.0) No cross-session similarity awareness | hindsight_recall check on every session start | Phase 5 + This phase | Agent can detect and offer to resume similar previous work. Saves time, reduces duplication. |
|
||||||
|
| (v1.0) Jira tickets created manually or not at all | Agent creates Task tickets per session (prompted) | This phase | Clear traceability from session work to Jira tickets. Epics link work to larger initiatives. |
|
||||||
|
| (v1.0) No session documentation in Confluence | Confluence pages tagged with `ngn-agent` | This phase | Searchable knowledge base of session outcomes. Team visibility. |
|
||||||
|
| (v1.0) No session summary persistence | automatic hindsight_retain of structured summary | Phase 5 + This phase | Future hindsight_recall queries find relevant past sessions. Compounding knowledge base. |
|
||||||
|
|
||||||
|
**Deprecated/outdated:**
|
||||||
|
- **Manual session setup:** Every v1.0 session required the user to manually say "check my last session," "create a ticket," etc. The session skill automates this in a repeatable workflow.
|
||||||
|
- **Missing session documentation:** Without D-12/D-13, past session work was invisible. The hindsight_retain at session end creates a searchable record.
|
||||||
|
|
||||||
|
## Assumptions Log
|
||||||
|
|
||||||
|
| # | Claim | Section | Risk if Wrong |
|
||||||
|
|---|-------|---------|---------------|
|
||||||
|
| A1 | The agent will reliably load the session skill when the "When to Use" conditions match | Architecture Patterns — Skill Loading | **Medium risk.** If the agent doesn't load the skill, the session workflow is skipped. Mitigation: Make "When to Use" very broad, include explicit trigger examples. User can always use `/session` or tell the agent to load the skill manually. |
|
||||||
|
| A2 | The agent understands and follows `skill_view("jira-query")` cross-skill loading instructions from within a procedure | Architecture Patterns — Cross-Skill Loading | **Low risk.** `skill_view` is a standard Hermes tool. The agent uses it when instructed. |
|
||||||
|
| A3 | `hindsight_recall` with a task description query returns session summaries that are identifiable as "previous sessions" | Architecture Patterns — Similarity Search | **Low risk.** Phase 5 config uses `recall_types: observation` and `memory_mode: hybrid`. Session summaries saved with `tier: "session-summary"` will be returned when semantically similar. |
|
||||||
|
| A4 | The `ngn-jira` and `ngn-confluence` scripts are mounted and working inside Docker | Standard Stack | **Low risk.** Already verified in Phase 4 (v1.0). Phase 6 confirmed Docker script mounts exist. |
|
||||||
|
| A5 | `hindsight_retain` accepts a `tier` field | Architecture Patterns — Summary Save | **Low risk.** The `hindsight_retain` tool from the Hermes plugin accepts structured content. The `tier` field is a convention for filtering during recall. Actual parameter names depend on the tool schema. |
|
||||||
|
| A6 | The `ngn-agent` Confluence tag doesn't yet exist and needs to be created | E2: Confluence Integration | **Low risk.** If there are already pages with this tag, even better — search will find them. If not, the first session-end update will create the first tagged page. |
|
||||||
|
| A7 | The skill file path `~/.hermes/skills/ngn-agent/session/SKILL.md` is the correct location for Hermes discovery | Standard Stack | **Very low risk.** Hermes scans `~/.hermes/skills/` recursively for SKILL.md files. All 4 existing ngn-agent skills use subdirectories under `~/.hermes/skills/ngn-agent/`. |
|
||||||
|
|
||||||
|
## Open Questions (RESOLVED)
|
||||||
|
|
||||||
|
1. **How does Hermes resolve skill names with nested directories?** — RESOLVED: Set `name: ngn-agent-session` in frontmatter matching the following pattern in existing 4 skills (directory name `session` with frontmatter name `ngn-agent-session`). Plan uses directory path `ngn-agent/session/SKILL.md` consistent with existing skills. Verification step (`hermes skills list`) will confirm discoverability.
|
||||||
|
|
||||||
|
2. **What happens when skill_view() is called from within another skill's procedure?** — RESOLVED: The Procedure encodes Jira and Confluence operations directly using `ngn-jira` and `ngn-confluence` CLI scripts (already mounted in Docker) instead of depending on `skill_view()` cross-skill loading. This avoids any skill context replacement issues. `skill_view` is optional for extra context only.
|
||||||
|
|
||||||
|
3. **Does the agent need to save the Jira ticket key in memory to reference it at session end?** — RESOLVED: The Procedure instructs the agent to explicitly note the ticket key after creation (e.g., "Save the ticket key (e.g., PROJ-123) — you'll need it in Step 5 to add a comment"). This places the key in the agent's active context for the session duration.
|
||||||
|
|
||||||
|
## Environment Availability
|
||||||
|
|
||||||
|
| Dependency | Required By | Available | Version | Fallback |
|
||||||
|
|------------|------------|-----------|---------|----------|
|
||||||
|
| Hermes skill system | SKILL.md file | ✓ | v0.16.x | — |
|
||||||
|
| `hindsight_recall` tool | Step 1 (similar session check) | ✓ (Phase 5) | — | — |
|
||||||
|
| `hindsight_retain` tool | Step 7 (session summary save) | ✓ (Phase 5) | — | — |
|
||||||
|
| `ngn-jira` script | Steps 2, 5 (Jira operations) | ✓ (Phase 4, mounted) | — | — |
|
||||||
|
| `ngn-confluence` script | Steps 3, 6 (Confluence operations) | ✓ (Phase 4, mounted) | — | — |
|
||||||
|
| `skill_view` tool | Cross-skill loading | ✓ (Hermes built-in) | — | — |
|
||||||
|
| Hermes skill directory | SKILL.md placement | ✓ | — | — |
|
||||||
|
| `skill_manage` tool | (optional) Agent-created skill patches | ✓ (Hermes built-in) | — | — |
|
||||||
|
|
||||||
|
**Missing dependencies with no fallback:** None — all tools and scripts are confirmed available from completed phases (4, 5, 6) and Hermes built-in capabilities.
|
||||||
|
|
||||||
|
**Missing dependencies with fallback:** None.
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
> Skipped — `workflow.nyquist_validation` is explicitly `false` in `.planning/config.json`.
|
||||||
|
|
||||||
|
## Security Domain
|
||||||
|
|
||||||
|
### Applicable ASVS Categories
|
||||||
|
|
||||||
|
| ASVS Category | Applies | Standard Control |
|
||||||
|
|---------------|---------|-----------------|
|
||||||
|
| V2 Authentication | no | No new authentication — reuses existing Jira/Confluence credentials via `ngn-*` scripts |
|
||||||
|
| V3 Session Management | no | Skill has no session state management |
|
||||||
|
| V4 Access Control | no | Skill is read-only orchestration; all mutations require user confirmation (D-08, D-11) |
|
||||||
|
| V5 Input Validation | no | No user input processed at the skill layer — user decisions are conversational |
|
||||||
|
| V6 Cryptography | no | No new cryptographic operations |
|
||||||
|
| V8 Data Protection | yes | Session summary saved to hindsight memory (local embedded PostgreSQL from Phase 5) |
|
||||||
|
|
||||||
|
### Known Threat Patterns
|
||||||
|
|
||||||
|
| Pattern | STRIDE | Standard Mitigation |
|
||||||
|
|---------|--------|---------------------|
|
||||||
|
| Prompt injection causing unauthorized Jira updates | Tampering | D-08: User must confirm before any Jira update. The Procedure explicitly says "ask the user first." |
|
||||||
|
| Prompt injection causing unauthorized Confluence updates | Tampering | D-11: User must confirm before any Confluence update. Same pattern as Jira. |
|
||||||
|
| Unintended hindsight_retain of sensitive information | Information Disclosure | The session summary is auto-saved (D-12) but only includes high-level outcomes, not raw conversation. Hindsight in local_embedded mode stores data locally. |
|
||||||
|
|
||||||
|
### Key Security Properties
|
||||||
|
|
||||||
|
- **User confirmation gates:** Every write operation (Jira create, Jira update, Confluence create, Confluence update) is gated behind explicit user approval in the skill procedure
|
||||||
|
- **Auto-save is high-level only:** The hindsight_retain summary is structured and contains only task-level information, not raw conversation transcripts
|
||||||
|
- **Read-only operations always allowed:** hindsight_recall (read) and Confluence search (read) do NOT require user confirmation — only writes do
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- **Hermes official skills documentation** — `~/.hermes/hermes-agent/website/docs/user-guide/features/skills.md` (852 lines) — Complete SKILL.md format reference, progressive disclosure, skill bundles, cross-skill patterns [VERIFIED: read in full]
|
||||||
|
- **4 existing ngn-agent SKILL.md files** — `~/.hermes/skills/ngn-agent/*/SKILL.md` — aws-diagnostics, jira, confluence, bitbucket — all follow identical frontmatter + When to Use + Procedure + Pitfalls format [VERIFIED: read all 4]
|
||||||
|
- **Phase 5 SUMMARY** — `.planning/phases/05-hindsight-memory-provider/05-01-SUMMARY.md` — Confirms hindsight tools active with `memory_mode: hybrid` (3 tools available) [VERIFIED: read]
|
||||||
|
- **Phase 6 SUMMARY** — `.planning/phases/06-default-repos-ssh-mount/06-01-SUMMARY.md` — Confirms repo mounts and `ngn-*` scripts available in Docker [VERIFIED: read]
|
||||||
|
- **CONTEXT.md** — D-01 through D-14 locked decisions for this phase [VERIFIED: read]
|
||||||
|
- **initial-plan.md** — `func session()` workflow specification [VERIFIED: read]
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- **Hermes research docs** — `.planning/research/hermes/SKILLS.md`, `EXTENSIBILITY.md`, `SUMMARY.md` — Skills system overview, plugin hook system, feature landscape [CITED: read]
|
||||||
|
- **FEATURES.md** — `.planning/research/FEATURES.md` — Feature dependencies, prioritization [CITED: read]
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence)
|
||||||
|
- **Skill name resolution behavior** — How Hermes resolves directory path vs frontmatter name for skill_view — not tested [ASSUMED: A1]
|
||||||
|
- **skill_view behavior within active skill** — Whether loading another skill replaces or appends context — not verified [ASSUMED: Open Question 2]
|
||||||
|
- **hindsight_retain parameter names** — Exact tool schema for `tier` field — inferred from Hindsight documentation [ASSUMED: A5]
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH — All tools and scripts confirmed available from completed phases (4, 5, 6) and Hermes built-in capabilities
|
||||||
|
- Architecture: HIGH — Patterns derived from Hermes official docs and 4 existing ngn-agent skills. Cross-skill orchestration is standard Hermes usage.
|
||||||
|
- Pitfalls: HIGH — Pitfalls based on known Hermes skill behavior, Hindsight tool characteristics, and session workflow constraints from D-01 through D-14
|
||||||
|
- Security: HIGH — All write operations gated behind user confirmation per locked decisions; read operations unrestricted
|
||||||
|
|
||||||
|
**Research date:** 2026-06-15
|
||||||
|
**Valid until:** 2026-07-15 (30 days — Hermes SKILL.md format is stable, Phase 5/6 dependencies are locked)
|
||||||
242
.planning/phases/08-cron-reporting/08-01-PLAN.md
Normal file
242
.planning/phases/08-cron-reporting/08-01-PLAN.md
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
---
|
||||||
|
phase: 08-cron-reporting
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- ~/.hermes/scripts/archive-stale-sessions.sh
|
||||||
|
- ~/.hermes/archive/sessions/
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- CRON-01
|
||||||
|
- CRON-02
|
||||||
|
user_setup: []
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Archive stale sessions script exists at ~/.hermes/scripts/archive-stale-sessions.sh and is executable"
|
||||||
|
- "Archive directory exists at ~/.hermes/archive/sessions/"
|
||||||
|
- "Daily report cron job 'ngn-daily-report' is registered and fires at 09:00 SGT"
|
||||||
|
- "Daily report loads both 'session' and 'jira-query' skills"
|
||||||
|
artifacts:
|
||||||
|
- path: "~/.hermes/scripts/archive-stale-sessions.sh"
|
||||||
|
provides: "Deterministic no_agent script for session export + prune"
|
||||||
|
min_lines: 35
|
||||||
|
must_contain:
|
||||||
|
- "DRY_RUN"
|
||||||
|
- "hermes sessions export"
|
||||||
|
- "hermes sessions prune --older-than 30 --yes"
|
||||||
|
- path: "~/.hermes/archive/sessions/"
|
||||||
|
provides: "Archive storage directory for stale session JSONL exports"
|
||||||
|
- path: "Hermes cron DB (job: ngn-daily-report)"
|
||||||
|
provides: "Daily report cron job entry in Hermes cron scheduler"
|
||||||
|
key_links:
|
||||||
|
- from: "archive-stale-sessions.sh"
|
||||||
|
to: "hermes sessions export"
|
||||||
|
via: "CLI call with positional output path"
|
||||||
|
pattern: "hermes sessions export"
|
||||||
|
- from: "archive-stale-sessions.sh"
|
||||||
|
to: "hermes sessions prune"
|
||||||
|
via: "CLI call with --older-than 30 --yes"
|
||||||
|
pattern: "hermes sessions prune --older-than 30 --yes"
|
||||||
|
- from: "ngn-daily-report cron job"
|
||||||
|
to: "session skill"
|
||||||
|
via: "--skill session in cron create"
|
||||||
|
pattern: "skill session"
|
||||||
|
- from: "ngn-daily-report cron job"
|
||||||
|
to: "jira-query skill"
|
||||||
|
via: "--skill jira-query in cron create"
|
||||||
|
pattern: "skill jira-query"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Create the archive script and register the daily report cron job.
|
||||||
|
|
||||||
|
**Purpose:** Lay the foundation for CRON-02 (stale session archive script) and implement CRON-01 + CRON-03 (daily report with Jira integration). The archive script is created with a dry-run toggle so the first archive fires in safe mode (export only, no prune). The daily report cron is a skill-backed job that loads the session and jira-query skills to enumerate active sessions, post Jira progress comments, and deliver a Telegram summary.
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
- `~/.hermes/scripts/archive-stale-sessions.sh` — no_agent archive script with DRY_RUN toggle
|
||||||
|
- `~/.hermes/archive/sessions/` — archive storage directory
|
||||||
|
- Hermes cron job `ngn-daily-report` registered in gateway scheduler
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-CONTEXT.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-RESEARCH.md
|
||||||
|
@/Users/bapung/.hermes/config.yaml
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/session/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/jira/SKILL.md
|
||||||
|
@.planning/phases/07-main-session-skill/07-01-SUMMARY.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Create archive-stale-sessions.sh with dry-run toggle + archive directory</name>
|
||||||
|
<files>
|
||||||
|
~/.hermes/scripts/archive-stale-sessions.sh
|
||||||
|
~/.hermes/archive/sessions/
|
||||||
|
</files>
|
||||||
|
<action>
|
||||||
|
Create the archive directory and the no_agent archive script.
|
||||||
|
|
||||||
|
**Archive directory:**
|
||||||
|
```bash
|
||||||
|
mkdir -p ~/.hermes/archive/sessions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Script file:** Create `~/.hermes/scripts/archive-stale-sessions.sh` with:
|
||||||
|
- `DRY_RUN=true` at top — safe default (export only, skip prune)
|
||||||
|
- `set -euo pipefail` for strict error handling
|
||||||
|
- `ARCHIVE_DIR="$HOME/.hermes/archive/sessions"` — archive location per D-12
|
||||||
|
- `mkdir -p "$ARCHIVE_DIR"` — ensure directory exists
|
||||||
|
- `TIMESTAMP=$(date +%Y%m%d_%H%M%S)` for unique filenames
|
||||||
|
- `OUTPUT_FILE="$ARCHIVE_DIR/sessions-${TIMESTAMP}.jsonl"` — date-stamped per D-11 corrected (export uses positional output arg, no `--output` flag — per RESEARCH.md Pitfall 1 and CLI verification)
|
||||||
|
- Step 1: `hermes sessions export "$OUTPUT_FILE"` — exports ALL sessions (no `--older-than` flag on export — per D-11 correction and RESEARCH.md §Critical finding)
|
||||||
|
- Step 2: Only if `$DRY_RUN = false`, run `hermes sessions prune --older-than 30 --yes` (uses `--yes` not `--confirm` — confirmed by RESEARCH.md Pitfall 2 and `hermes sessions prune --help`)
|
||||||
|
- Step 3: `hermes sessions stats` — show post-archive store state
|
||||||
|
- Echo progress messages for Telegram delivery (stdout is delivered verbatim via `--deliver telegram`)
|
||||||
|
- Print summary with session count and file size
|
||||||
|
|
||||||
|
**Critical corrections from research (must NOT repeat CONTEXT.md errors):**
|
||||||
|
- ❌ `hermes sessions export --older-than 30d --output <path>` — DOES NOT EXIST. Use positional path only.
|
||||||
|
- ❌ `hermes sessions prune --confirm` — DOES NOT EXIST. Use `--yes`.
|
||||||
|
- ✅ Export all, prune separate: `hermes sessions export <path> && hermes sessions prune --older-than 30 --yes`
|
||||||
|
|
||||||
|
After creating the file, make it executable:
|
||||||
|
```bash
|
||||||
|
chmod +x ~/.hermes/scripts/archive-stale-sessions.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Dry-run verification:** Script's default mode is export-only (DRY_RUN=true). User manually reviews the first JSONL export before setting DRY_RUN=false to enable pruning. This satisfies the user's discretion requirement for safe initial testing.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>
|
||||||
|
test -f ~/.hermes/scripts/archive-stale-sessions.sh \
|
||||||
|
&& test -x ~/.hermes/scripts/archive-stale-sessions.sh \
|
||||||
|
&& test -d ~/.hermes/archive/sessions \
|
||||||
|
&& grep -q 'DRY_RUN' ~/.hermes/scripts/archive-stale-sessions.sh \
|
||||||
|
&& grep -q 'hermes sessions export' ~/.hermes/scripts/archive-stale-sessions.sh \
|
||||||
|
&& grep -q 'hermes sessions prune --older-than 30' ~/.hermes/scripts/archive-stale-sessions.sh \
|
||||||
|
&& grep -q '--yes' ~/.hermes/scripts/archive-stale-sessions.sh \
|
||||||
|
&& grep -q 'set -euo pipefail' ~/.hermes/scripts/archive-stale-sessions.sh
|
||||||
|
</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
Archive script exists at ~/.hermes/scripts/archive-stale-sessions.sh, is executable, and contains:
|
||||||
|
- DRY_RUN=true toggle (safe default)
|
||||||
|
- Export with date-stamped filename
|
||||||
|
- Prune with `--older-than 30 --yes` (gated behind dry-run)
|
||||||
|
- `set -euo pipefail` error handling
|
||||||
|
- Progress echo statements for Telegram delivery
|
||||||
|
Archive directory exists at ~/.hermes/archive/sessions/
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Register daily report cron job (09:00 SGT, skill-backed)</name>
|
||||||
|
<files>(Hermes cron DB — internal state)</files>
|
||||||
|
<action>
|
||||||
|
Register the daily report cron job using `hermes cron create`.
|
||||||
|
|
||||||
|
**Command (execute verbatim):**
|
||||||
|
```bash
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-daily-report" \
|
||||||
|
--deliver telegram \
|
||||||
|
--skill session \
|
||||||
|
--skill jira-query \
|
||||||
|
"0 9 * * *" \
|
||||||
|
"Daily operational report — generate summary of active sessions and update their Jira tickets:
|
||||||
|
|
||||||
|
1. DISCOVER ACTIVE SESSIONS: Run 'hermes sessions export -' to get all sessions as JSONL. Use python3 to parse the JSONL and find sessions with last_active within the last 7 days — these are the active sessions. For each active session, record its id, title, and last_active timestamp. (Note: 'hermes sessions list' has NO --json flag — use export - for machine-readable output per RESEARCH.md Pitfall 3.)
|
||||||
|
|
||||||
|
2. FIND JIRA TICKETS: For each active session, use hindsight_recall with query 'session summary jira' to find the Jira ticket key from the session summary saved by Phase 7's session skill (Step 7, tier: session-summary). If hindsight_recall returns no results, search session messages from the export output for Jira ticket key patterns (PLATFORM-<digits>, AIOPS-<digits>, etc.) — per RESEARCH.md Pitfall 4. One session may have multiple Jira tickets (1-to-many mapping — D-04).
|
||||||
|
|
||||||
|
3. UPDATE JIRA TICKETS: For each active session with found Jira ticket key(s), add a progress comment via:
|
||||||
|
ngn-jira POST '/rest/api/3/issue/<KEY>/comment' --body '{\"body\": \"Session activity update — Date: <today>, Last active: <last_active>. Session: <session_id>. Progress: See session transcript for details.\"}'
|
||||||
|
IMPORTANT: Do NOT transition ticket statuses (D-05). Only add comments. Do NOT update tickets for stale sessions (D-15).
|
||||||
|
|
||||||
|
4. COMPOSE TELEGRAM SUMMARY: Create a structured message with sections:
|
||||||
|
📋 ACTIVE SESSIONS — list each with title, last active timestamp
|
||||||
|
🔄 JIRA UPDATED — list of ticket keys that received new comments
|
||||||
|
Keep within Telegram's 4096 character limit.
|
||||||
|
Do NOT include stale session info or ticket status transitions.
|
||||||
|
|
||||||
|
5. DELIVERY: Your response is automatically delivered to TELEGRAM_HOME_CHANNEL via --deliver telegram."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key details (per locked decisions):**
|
||||||
|
- Per D-01: Schedule `0 9 * * *` = daily at 09:00 SGT. Timezone is already SGT (+08, confirmed by date +%Z) — no TZ config needed per RESEARCH.md Pitfall 5.
|
||||||
|
- Per D-06: `--deliver telegram` sends to TELEGRAM_HOME_CHANNEL (474440517)
|
||||||
|
- Per D-17: Skill-backed cron (two `--skill` flags, no `--no-agent`)
|
||||||
|
- Per D-18: Loads `session` skill (Phase 7) for session structure + `jira-query` skill (Phase 4) for Jira comment patterns
|
||||||
|
- Per D-04/D-14: Updates ALL Jira tickets linked to active sessions (1-to-many mapping)
|
||||||
|
- Per D-05/D-16: Comments only, no status transitions
|
||||||
|
- Per D-07: Report includes active sessions list, last activity timestamps, Jira ticket keys updated
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>
|
||||||
|
hermes cron list 2>&1 | grep -q ngn-daily-report
|
||||||
|
</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
Daily report cron job `ngn-daily-report` is registered with:
|
||||||
|
- Schedule: `0 9 * * *` (daily at 09:00)
|
||||||
|
- Delivery: telegram (TELEGRAM_HOME_CHANNEL)
|
||||||
|
- Skills: session + jira-query (skill-backed, no no-agent)
|
||||||
|
- Prompt instructs agent to: enumerate active sessions via JSONL export, find Jira tickets via hindsight_recall, add progress comments via ngn-jira, compose Telegram summary
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Cron agent → Hermes session DB | Cron LLM agent reads session data (ids, titles, timestamps, transcripts) to compose reports |
|
||||||
|
| Cron agent → Jira Cloud API | Cron LLM agent writes progress comments to Jira tickets via ngn-jira script |
|
||||||
|
| no_agent script → Hermes session DB | Archive script reads (export) and writes (prune) session store directly |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-08-01 | Tampering | Daily report cron prompt | accept | Cron prompt is authored during plan execution and stored in Hermes cron DB — not user-controllable. No injection risk. |
|
||||||
|
| T-08-02 | Information Disclosure | Archive JSONL files | mitigate | Archive directory (`~/.hermes/archive/sessions/`) is under `~/.hermes/` which has same protection as Hermes data dir. No world-readable permissions. |
|
||||||
|
| T-08-03 | Information Disclosure | Cron output to Telegram | accept | Report summaries sent to user's DM channel only (TELEGRAM_HOME_CHANNEL). No PII in summaries — session IDs and Jira keys only. |
|
||||||
|
| T-08-04 | Tampering | Session prune via no_agent script | mitigate | DRY_RUN=true by default — no prune until user explicitly sets false. Also `approvals.cron_mode: deny` in config adds an approval gate for destructive cron operations per RESEARCH.md Security Domain. |
|
||||||
|
| T-08-05 | Elevation of Privilege | Jira comment injection | accept | Cron agent uses ngn-jira script which authenticates with JIRA_EMAIL + JIRA_API_TOKEN — bounded to the user's Jira permissions. Comments only, no transitions per D-05/D-16. |
|
||||||
|
| T-08-SC | Tampering | Package installs | n/a | No packages installed — all tools are native Hermes CLI or existing scripts. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. `test -f ~/.hermes/scripts/archive-stale-sessions.sh && test -x ~/.hermes/scripts/archive-stale-sessions.sh` — script exists and is executable
|
||||||
|
2. `test -d ~/.hermes/archive/sessions` — archive directory exists
|
||||||
|
3. `grep -q DRY_RUN ~/.hermes/scripts/archive-stale-sessions.sh` — dry-run toggle present
|
||||||
|
4. `grep -q 'hermes sessions export' ~/.hermes/scripts/archive-stale-sessions.sh && grep -q 'hermes sessions prune --older-than 30 --yes' ~/.hermes/scripts/archive-stale-sessions.sh` — correct CLI commands used
|
||||||
|
5. `hermes cron list 2>&1 | grep -q ngn-daily-report` — daily report cron job registered
|
||||||
|
6. `hermes cron run ngn-daily-report 2>&1` — test-run succeeds (schedules for immediate execution; verify no errors)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Archive script at ~/.hermes/scripts/archive-stale-sessions.sh is executable with DRY_RUN=true default
|
||||||
|
- Archive directory at ~/.hermes/archive/sessions/ exists
|
||||||
|
- Daily report cron job `ngn-daily-report` is registered and visible in `hermes cron list`
|
||||||
|
- Daily report uses correct schedule (0 9 * * *), delivery (telegram), and skills (session + jira-query)
|
||||||
|
- Test-run of daily report cron job completes without CLI errors
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/08-cron-reporting/08-01-SUMMARY.md` when done.
|
||||||
|
</output>
|
||||||
125
.planning/phases/08-cron-reporting/08-01-SUMMARY.md
Normal file
125
.planning/phases/08-cron-reporting/08-01-SUMMARY.md
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
---
|
||||||
|
phase: 08-cron-reporting
|
||||||
|
plan: 01
|
||||||
|
subsystem: cron
|
||||||
|
tags: [hermes, cron, session, archive, telegram, jira]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 07-main-session-skill
|
||||||
|
provides: session skill loaded by daily report cron for session structure; session-summary hindsight entries for Jira key discovery
|
||||||
|
- phase: 04-jira-skill
|
||||||
|
provides: jira-query skill loaded by daily report cron for Jira comment patterns and ngn-jira CLI
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- archive-stale-sessions.sh — no_agent deterministic archive script with DRY_RUN toggle
|
||||||
|
- ~/.hermes/archive/sessions/ — archive storage directory for stale session JSONL exports
|
||||||
|
- ngn-daily-report cron job — skill-backed daily report registered in Hermes cron scheduler
|
||||||
|
|
||||||
|
affects:
|
||||||
|
- Phase 8 Plan 2 (08-02-PLAN.md) — weekly stale summary + archive cron comes next
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- no_agent deterministic script with DRY_RUN toggle pattern for safe-first archive operations
|
||||||
|
- Skill-backed cron registration with dual skills (session + jira-query)
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- ~/.hermes/scripts/archive-stale-sessions.sh — no_agent archive script (1146 bytes, executable)
|
||||||
|
- ~/.hermes/archive/sessions/ — archive storage directory
|
||||||
|
- Hermes cron job: ngn-daily-report (skill-backed)
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "DRY_RUN=true default ensures first archive is export-only (safe review before enabling prune)"
|
||||||
|
- "archive-stale-sessions.sh uses correct CLI: hermes sessions export <path> (positional, no --output flag) and hermes sessions prune --older-than 30 --yes (not --confirm)"
|
||||||
|
- "Daily report cron loads both session (Phase 7) and jira-query (Phase 4) skills for LLM-guided reporting"
|
||||||
|
- "Cron prompt instructs agent to use hermes sessions export - for machine-readable session data (not list --json which doesn't exist)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "no_agent script: DRY_RUN=true default → export → optional prune with --older-than 30 --yes"
|
||||||
|
- "Skill-backed cron: hermes cron create --deliver telegram --skill <A> --skill <B> 'schedule' 'prompt'"
|
||||||
|
|
||||||
|
requirements-completed: [CRON-01, CRON-02]
|
||||||
|
|
||||||
|
duration: ~1 min
|
||||||
|
completed: 2026-06-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 8 Plan 1: Archive Script + Daily Report Cron Summary
|
||||||
|
|
||||||
|
**Archive script at ~/.hermes/scripts/archive-stale-sessions.sh (DRY_RUN=true safe default) + ngn-daily-report cron job (09:00 SGT, skill-backed with session + jira-query skills)**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~1 min
|
||||||
|
- **Started:** 2026-06-15T14:44:42Z
|
||||||
|
- **Completed:** 2026-06-15T14:45:37Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 2 (outside repo — Hermes system files)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Created `archive-stale-sessions.sh` with `DRY_RUN=true` safe default (export-only), `set -euo pipefail` strict mode, progress echo messages for Telegram delivery
|
||||||
|
- Archive script correctly uses `hermes sessions export <path>` (positional arg, no `--output`) and `hermes sessions prune --older-than 30 --yes` (not `--confirm`) — all CLI corrections from RESEARCH.md applied
|
||||||
|
- Created `~/.hermes/archive/sessions/` archive storage directory
|
||||||
|
- Registered `ngn-daily-report` cron job at 09:00 SGT with Telegram delivery, skill-backed with `session` and `jira-query` skills
|
||||||
|
- Cron job next run confirmed: `2026-06-16T09:00:00+08:00` (correct SGT timezone)
|
||||||
|
- Test-run triggered successfully — no CLI errors
|
||||||
|
- Both CRON-01 (daily report) and CRON-02 (stale session archive) requirements addressed
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Create archive-stale-sessions.sh with dry-run toggle + archive directory** — `47d0b80` (feat)
|
||||||
|
2. **Task 2: Register daily report cron job (09:00 SGT, skill-backed)** — `8db45eb` (feat)
|
||||||
|
|
||||||
|
**Plan metadata:** See final metadata commit for SUMMARY.md.
|
||||||
|
|
||||||
|
_Note: Script and cron job are outside project git repo — committed with --allow-empty descriptors._
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `~/.hermes/scripts/archive-stale-sessions.sh` — no_agent archive script (1146 bytes, executable, DRY_RUN=true)
|
||||||
|
- `~/.hermes/archive/sessions/` — Archive storage directory for stale session JSONL exports
|
||||||
|
- Hermes cron DB: `ngn-daily-report` job registered (schedule: `0 9 * * *`, skills: session + jira-query, delivery: telegram)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **DRY_RUN=true default**: Export-only on first run — user reviews JSONL before enabling prune. Safe default per user discretion requirement (CONTEXT.md).
|
||||||
|
- **CLI corrections from RESEARCH.md**: All three Pitfalls applied:
|
||||||
|
1. `hermes sessions export <path>` (positional, no `--output` flag)
|
||||||
|
2. `hermes sessions prune --older-than 30 --yes` (not `--confirm`)
|
||||||
|
3. Cron prompt instructs agent to use `hermes sessions export -` (not `list --json` which doesn't exist)
|
||||||
|
- **Dual skills on daily report**: Both `session` (for session structure, hindsight mapping) and `jira-query` (for Jira comment API patterns) loaded as skill-backed cron
|
||||||
|
- **Prompt detail**: Full 5-step prompt embedded in cron job — agent receives complete instructions for session discovery, Jira key lookup, ticket updates, Telegram composition
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
- None. Archive script creation, directory creation, and cron registration all succeeded on first attempt.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — all threat mitigations from the plan's threat model are satisfied:
|
||||||
|
- T-08-01 (Tampering — cron prompt injection): Accepted — prompt is authored during plan execution, not user-controllable.
|
||||||
|
- T-08-02 (Information Disclosure — archive JSONL files): Mitigated — archive directory under `~/.hermes/` with same protection as Hermes data dir.
|
||||||
|
- T-08-03 (Information Disclosure — cron to Telegram): Accepted — sent to TELEGRAM_HOME_CHANNEL (user's DM) only.
|
||||||
|
- T-08-04 (Tampering — session prune): Mitigated — DRY_RUN=true by default, plus `approvals.cron_mode: deny` in config.
|
||||||
|
- T-08-05 (Elevation of Privilege — Jira comment injection): Accepted — bounded to user's Jira permissions, comments only.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Archive script ready for weekly cron registration (Phase 8, Plan 2 — weekly stale summary + archive)
|
||||||
|
- Daily report cron `ngn-daily-report` will fire at 09:00 SGT daily
|
||||||
|
- Phase 8 Plan 2 can register `ngn-weekly-stale-summary` (skill-backed, Sunday 20:00 SGT) and `ngn-weekly-archive` (no_agent script, Sunday 20:05 SGT)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 08-cron-reporting*
|
||||||
|
*Completed: 2026-06-15*
|
||||||
201
.planning/phases/08-cron-reporting/08-02-PLAN.md
Normal file
201
.planning/phases/08-cron-reporting/08-02-PLAN.md
Normal file
@@ -0,0 +1,201 @@
|
|||||||
|
---
|
||||||
|
phase: 08-cron-reporting
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 2
|
||||||
|
depends_on:
|
||||||
|
- 08-01
|
||||||
|
files_modified: []
|
||||||
|
autonomous: true
|
||||||
|
requirements:
|
||||||
|
- CRON-02
|
||||||
|
- CRON-03
|
||||||
|
user_setup: []
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Weekly stale summary cron 'ngn-weekly-stale-summary' fires at Sunday 20:00 SGT"
|
||||||
|
- "Weekly archive cron 'ngn-weekly-archive' fires at Sunday 20:05 SGT (5 min after summary)"
|
||||||
|
- "Weekly stale summary loads 'session' skill for understanding session structure"
|
||||||
|
- "Weekly archive runs as no_agent (script archive-stale-sessions.sh, no LLM cost)"
|
||||||
|
- "Archive cron schedule is offset by 5 min to avoid race condition with summary"
|
||||||
|
artifacts:
|
||||||
|
- path: "Hermes cron DB (job: ngn-weekly-stale-summary)"
|
||||||
|
provides: "Weekly stale session summary cron entry"
|
||||||
|
- path: "Hermes cron DB (job: ngn-weekly-archive)"
|
||||||
|
provides: "Weekly archive cron entry"
|
||||||
|
key_links:
|
||||||
|
- from: "ngn-weekly-stale-summary cron"
|
||||||
|
to: "session skill"
|
||||||
|
via: "--skill session in cron create"
|
||||||
|
pattern: "skill session"
|
||||||
|
- from: "ngn-weekly-archive cron"
|
||||||
|
to: "archive-stale-sessions.sh"
|
||||||
|
via: "--script archive-stale-sessions.sh --no-agent"
|
||||||
|
pattern: "archive-stale-sessions"
|
||||||
|
- from: "ngn-weekly-archive cron"
|
||||||
|
to: "ngn-weekly-stale-summary cron"
|
||||||
|
via: "5 minute offset (20:05 vs 20:00) to avoid race condition"
|
||||||
|
pattern: "5 20"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
Register two weekly cron jobs: stale session summary and stale session archive.
|
||||||
|
|
||||||
|
**Purpose:** Implement CRON-02's weekly archive (complete the stale session lifecycle) and CRON-03's stale session Jira awareness (weekly summary mentions Jira ticket keys for stale sessions). The weekly summary is skill-backed (uses session skill to understand session structure and hindsight for Jira-session mapping discovery). The weekly archive is no_agent (deterministic export + prune via the script created in Plan 1). The 5-minute offset between summary (20:00) and archive (20:05) prevents a race condition where the prune removes sessions the summary agent is still enumerating — per RESEARCH.md Anti-Patterns.
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
- Hermes cron job `ngn-weekly-stale-summary` registered (Sunday 20:00 SGT)
|
||||||
|
- Hermes cron job `ngn-weekly-archive` registered (Sunday 20:05 SGT)
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/PROJECT.md
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/REQUIREMENTS.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-CONTEXT.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-RESEARCH.md
|
||||||
|
@/Users/bapung/.hermes/config.yaml
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/session/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/jira/SKILL.md
|
||||||
|
@.planning/phases/07-main-session-skill/07-01-SUMMARY.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-01-SUMMARY.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Register weekly stale session summary cron (Sunday 20:00 SGT, skill-backed)</name>
|
||||||
|
<files>(Hermes cron DB — internal state)</files>
|
||||||
|
<action>
|
||||||
|
Register the weekly stale session summary cron job using `hermes cron create`.
|
||||||
|
|
||||||
|
**Command (execute verbatim):**
|
||||||
|
```bash
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-weekly-stale-summary" \
|
||||||
|
--deliver telegram \
|
||||||
|
--skill session \
|
||||||
|
"0 20 * * 0" \
|
||||||
|
"Weekly stale session summary — report on sessions inactive for more than 30 days:
|
||||||
|
|
||||||
|
1. FIND STALE SESSIONS: Run 'hermes sessions export -' to get all sessions as JSONL. Use python3 to parse and filter for sessions where last_active is more than 30 days ago. For each stale session, record its id, title, and last_active timestamp. (Note: 'hermes sessions list' has NO --json flag — use export - for machine-readable output per RESEARCH.md Pitfall 3.)
|
||||||
|
|
||||||
|
2. DISCOVER JIRA TICKETS: For each stale session, use hindsight_recall with query 'session summary jira' to find the Jira ticket key from the session summary (saved by Phase 7 session skill, Step 7, tier: session-summary). If hindsight_recall returns no results, search session messages for Jira key patterns (PLATFORM-<digits>, AIOPS-<digits>, etc.).
|
||||||
|
|
||||||
|
3. REPORT ONLY — NO JIRA MUTATIONS: Do NOT add comments to Jira tickets. Do NOT transition ticket statuses. Only report their keys in the summary (per D-10 and D-15).
|
||||||
|
|
||||||
|
4. COMPOSE TELEGRAM SUMMARY: Create a structured message with sections:
|
||||||
|
⏳ STALE SESSIONS (>30d inactive) — list each with:
|
||||||
|
- Session title
|
||||||
|
- Last activity date
|
||||||
|
- Linked Jira ticket key (or 'no ticket')
|
||||||
|
Keep within Telegram's 4096 character limit. Do not include active sessions.
|
||||||
|
|
||||||
|
5. DELIVERY: Your response is automatically delivered to TELEGRAM_HOME_CHANNEL via --deliver telegram."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key details (per locked decisions):**
|
||||||
|
- Per D-02: Schedule `0 20 * * 0` = Sunday at 20:00 SGT. Timezone is already SGT (+08) — no TZ config needed.
|
||||||
|
- Per D-08: Summarizes inactive sessions (>30d since last activity) with session title, last activity date, linked Jira ticket
|
||||||
|
- Per D-09: Delivered via Telegram to user's DM channel
|
||||||
|
- Per D-10/D-15: No auto-comments on stale tickets — only reported in summary
|
||||||
|
- Per D-17: Skill-backed cron (one `--skill session`, no `--no-agent`)
|
||||||
|
- Uses `--skill session` (Phase 7) for understanding session structure and transaction patterns
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>
|
||||||
|
hermes cron list 2>&1 | grep -q ngn-weekly-stale-summary
|
||||||
|
</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
Weekly stale summary cron `ngn-weekly-stale-summary` is registered with:
|
||||||
|
- Schedule: `0 20 * * 0` (Sunday at 20:00 SGT)
|
||||||
|
- Delivery: telegram (TELEGRAM_HOME_CHANNEL)
|
||||||
|
- Skill: session (skill-backed, no no-agent)
|
||||||
|
- Prompt instructs agent to: enumerate stale sessions via JSONL export, discover Jira ticket keys via hindsight_recall, compose Telegram summary without Jira mutations
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Register weekly archive cron (Sunday 20:05 SGT, no_agent)</name>
|
||||||
|
<files>(Hermes cron DB — internal state)</files>
|
||||||
|
<action>
|
||||||
|
Register the weekly stale session archive cron job using `hermes cron create` with `--no-agent` and `--script` flags.
|
||||||
|
|
||||||
|
**Command (execute verbatim):**
|
||||||
|
```bash
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-weekly-archive" \
|
||||||
|
--deliver telegram \
|
||||||
|
--script archive-stale-sessions.sh \
|
||||||
|
--no-agent \
|
||||||
|
"5 20 * * 0"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key details (per locked decisions):**
|
||||||
|
- Per D-03 (corrected): Schedule `5 20 * * 0` = Sunday at 20:05 SGT — 5 minutes after the stale summary. This prevents a race condition where the prune might delete sessions the summary agent is still enumerating (per RESEARCH.md Anti-Pattern: Race condition on same schedule).
|
||||||
|
- Per D-11 (corrected): Uses native Hermes commands (`hermes sessions export` + `hermes sessions prune --older-than 30 --yes`). The script was created in Plan 1 (08-01) with all research corrections applied (no --older-than on export, --yes on prune).
|
||||||
|
- Per D-12: Archive files stored in `~/.hermes/archive/sessions/sessions-<timestamp>.jsonl`
|
||||||
|
- Per D-13: Runs weekly after the stale summary report
|
||||||
|
- Per D-19: Uses `--no-agent` for deterministic CLI operations (no LLM cost)
|
||||||
|
- The script at `~/.hermes/scripts/archive-stale-sessions.sh` was created in Plan 1 and has `DRY_RUN=true` by default (export only, skip prune). User must manually verify the first export and set DRY_RUN=false to enable pruning.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>
|
||||||
|
hermes cron list 2>&1 | grep -q ngn-weekly-archive
|
||||||
|
</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
Weekly archive cron `ngn-weekly-archive` is registered with:
|
||||||
|
- Schedule: `5 20 * * 0` (Sunday at 20:05 SGT — 5 min after summary to avoid race condition)
|
||||||
|
- Delivery: telegram (stdout of archive-stale-sessions.sh delivered verbatim)
|
||||||
|
- Mode: --no-agent (no LLM cost)
|
||||||
|
- Script: archive-stale-sessions.sh (from ~/.hermes/scripts/, created in Plan 1)
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Cron agent → Hermes session DB | Weekly summary LLM agent reads session data (ids, titles, timestamps) to identify stale sessions |
|
||||||
|
| no_agent script → Hermes session DB | Archive script reads (export) and writes (prune) session store directly with CLI permissions |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-08-06 | Tampering | Weekly stale summary cron prompt | accept | Prompt is authored during plan execution and stored in Hermes cron DB — not user-controllable. No injection risk. |
|
||||||
|
| T-08-07 | Repudiation | Stale session prune | mitigate | Archive script's DRY_RUN=true default means no prune until user flips the flag. All exports produce timestamped JSONL files that serve as audit trail of which sessions existed before pruning. |
|
||||||
|
| T-08-08 | Denial of Service | Race condition: summary + archive at same time | mitigate | Archive scheduled at 20:05 (5 min after summary at 20:00) — prevents prune-from-underneath during summary enumeration per RESEARCH.md §Anti-Patterns to Avoid. |
|
||||||
|
| T-08-SC | Tampering | Package installs | n/a | No packages installed — all tools are native Hermes CLI or existing scripts. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
1. `hermes cron list 2>&1 | grep -q ngn-weekly-stale-summary` — weekly summary cron job registered
|
||||||
|
2. `hermes cron list 2>&1 | grep -q ngn-weekly-archive` — weekly archive cron job registered
|
||||||
|
3. `hermes cron list 2>&1 | grep 'ngn-weekly-stale-summary' | grep -q '0 20 \* \* 0'` — correct schedule (Sunday 20:00 SGT)
|
||||||
|
4. `hermes cron list 2>&1 | grep 'ngn-weekly-archive' | grep -q '5 20 \* \* 0'` — correct schedule (Sunday 20:05 SGT)
|
||||||
|
5. `hermes cron run ngn-weekly-stale-summary 2>&1` — test-run succeeds (schedules for immediate execution; verify no CLI errors)
|
||||||
|
6. `hermes cron run ngn-weekly-archive 2>&1` — test-run succeeds (schedules for immediate execution; verify no CLI errors)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
- Weekly stale summary cron `ngn-weekly-stale-summary` registered with schedule `0 20 * * 0`, delivery telegram, skill session
|
||||||
|
- Weekly archive cron `ngn-weekly-archive` registered with schedule `5 20 * * 0`, delivery telegram, no_agent mode, script archive-stale-sessions.sh
|
||||||
|
- Both jobs visible in `hermes cron list` with correct schedules
|
||||||
|
- Archive cron runs 5 min AFTER summary cron to prevent race condition (per Research Anti-Pattern)
|
||||||
|
- Test-run of both cron jobs completes without CLI errors
|
||||||
|
</success_criteria>
|
||||||
|
|
||||||
|
<output>
|
||||||
|
Create `.planning/phases/08-cron-reporting/08-02-SUMMARY.md` when done.
|
||||||
|
</output>
|
||||||
121
.planning/phases/08-cron-reporting/08-02-SUMMARY.md
Normal file
121
.planning/phases/08-cron-reporting/08-02-SUMMARY.md
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
---
|
||||||
|
phase: 08-cron-reporting
|
||||||
|
plan: 02
|
||||||
|
subsystem: cron
|
||||||
|
tags: [hermes, cron, session, archive, telegram, stale-sessions]
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 07-main-session-skill
|
||||||
|
provides: session skill loaded by weekly summary cron for session structure and hysteresis recall of Jira keys
|
||||||
|
- phase: 08-cron-reporting
|
||||||
|
plan: 01
|
||||||
|
provides: archive-stale-sessions.sh script for deterministic no_agent archive
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- ngn-weekly-stale-summary cron — skill-backed weekly stale session summary (Sunday 20:00 SGT)
|
||||||
|
- ngn-weekly-archive cron — no_agent deterministic archive (Sunday 20:05 SGT, 5 min after summary)
|
||||||
|
|
||||||
|
affects:
|
||||||
|
- Phase 9 (tool provisioning) if archive verification requires adjustments
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- Skill-backed cron with --skill session for LLM-guided stale session summary
|
||||||
|
- no_agent cron with --script archive-stale-sessions.sh for deterministic CLI archive
|
||||||
|
- 5-minute offset between complementary cron jobs to avoid race conditions
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- Hermes cron job: ngn-weekly-stale-summary (skill-backed, Sunday 20:00 SGT)
|
||||||
|
- Hermes cron job: ngn-weekly-archive (no_agent, Sunday 20:05 SGT)
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Weekly summary cron is skill-backed (--skill session) for understanding session structure and Jira-session mapping via hysteresis"
|
||||||
|
- "Weekly archive cron is no_agent (--no-agent --script archive-stale-sessions.sh) for deterministic CLI operations with no LLM cost"
|
||||||
|
- "Archive runs 5 min AFTER summary (20:05 vs 20:00) to prevent race condition where prune deletes sessions the summary agent is enumerating"
|
||||||
|
- "Summary cron prompt instructs agent to: use 'hermes sessions export -' (not list --json), discover Jira keys via hysteresis_recall, and report only with no Jira mutations (per D-10/D-15)"
|
||||||
|
- "Archive runs in DRY_RUN=true mode by default (script from Plan 1) — export-only until user flips to enable pruning"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Complementary cron offset: summary at :00, archive at :05 — prevents prune-from-underneath during summary enumeration"
|
||||||
|
- "no_agent archive: hermes cron create --no-agent --script <name> 'schedule' — deterministic CLI operations registered as cron jobs"
|
||||||
|
|
||||||
|
requirements-completed: [CRON-02, CRON-03]
|
||||||
|
|
||||||
|
duration: ~1 min
|
||||||
|
completed: 2026-06-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 8 Plan 2: Weekly Stale Summary + Archive Cron Summary
|
||||||
|
|
||||||
|
**Two weekly cron jobs registered: ngn-weekly-stale-summary (Sunday 20:00 SGT, skill-backed) and ngn-weekly-archive (Sunday 20:05 SGT, no_agent with archive-stale-sessions.sh) — completing the stale session lifecycle and Jira-awareness reporting**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** ~1 min
|
||||||
|
- **Started:** 2026-06-15T14:46:32Z
|
||||||
|
- **Completed:** 2026-06-15T14:47:26Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 2 (Hermes cron DB entries — outside project repo)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Registered `ngn-weekly-stale-summary` cron at Sunday 20:00 SGT with `--skill session` — LLM agent loads session skill to understand session structure, enumerate stale sessions via JSONL export, discover Jira ticket keys via hysteresis_recall, and compose Telegram summary (no Jira mutations per D-10/D-15)
|
||||||
|
- Registered `ngn-weekly-archive` cron at Sunday 20:05 SGT with `--no-agent --script archive-stale-sessions.sh` — deterministic CLI archive that exports sessions to JSONL and optionally prunes stale ones (DRY_RUN=true default from Plan 1)
|
||||||
|
- 5-minute offset between summary (20:00) and archive (20:05) prevents race condition where prune could delete sessions the summary agent is still enumerating
|
||||||
|
- Both cron jobs visible in `hermes cron list` with correct schedules
|
||||||
|
- Test-run of both jobs succeeded without CLI errors
|
||||||
|
- Requirements CRON-02 (weekly archive) and CRON-03 (Jira-awareness in reports) both addressed
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Register weekly stale session summary cron (Sunday 20:00 SGT, skill-backed)** — `90214dd` (feat)
|
||||||
|
2. **Task 2: Register weekly archive cron (Sunday 20:05 SGT, no_agent)** — `2faeb0a` (feat)
|
||||||
|
|
||||||
|
**Plan metadata:** See final metadata commit.
|
||||||
|
|
||||||
|
_Note: Cron jobs are registered in Hermes internal DB — committed with --allow-empty descriptors._
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- Hermes cron DB: `ngn-weekly-stale-summary` job (schedule: `0 20 * * 0`, skills: session, delivery: telegram, prompt: stale session summary with Jira discovery)
|
||||||
|
- Hermes cron DB: `ngn-weekly-archive` job (schedule: `5 20 * * 0`, mode: no-agent, script: archive-stale-sessions.sh, delivery: telegram)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **Skill-backed summary vs no_agent archive**: The summary cron uses `--skill session` because it needs LLM reasoning to understand session structure, identify stale sessions, and compose natural-language Telegram messages. The archive cron uses `--no-agent` because it only runs deterministic CLI commands (export + prune) with no LLM cost.
|
||||||
|
- **5-minute offset**: Archive at 20:05, 5 minutes after summary at 20:00. This follows RESEARCH.md Anti-Patterns guidance — prevents race condition where the prune could remove sessions the summary agent is still enumerating.
|
||||||
|
- **Prompt detail**: Full 5-step prompt embedded in summary cron — agent receives complete instructions for stale session discovery via JSONL export, Jira key lookup via hysteresis_recall, Telegram composition with 4096-char limit, and explicit prohibition against Jira mutations.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
- None. Both cron registrations succeeded on first attempt.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — all threat mitigations from the plan's threat model are satisfied:
|
||||||
|
- T-08-06 (Tampering — weekly stale summary cron prompt): Accepted — prompt is authored during plan execution, not user-controllable.
|
||||||
|
- T-08-07 (Repudiation — stale session prune): Mitigated — archive script's DRY_RUN=true default means no prune until user flips the flag; timestamped JSONL exports serve as audit trail.
|
||||||
|
- T-08-08 (Denial of Service — race condition): Mitigated — archive at 20:05 (5 min after summary at 20:00), preventing prune-from-underneath during summary enumeration.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Stale session lifecycle complete: summary (Sunday 20:00 SGT) + archive (Sunday 20:05 SGT) automated
|
||||||
|
- Daily report already running at 09:00 SGT from Plan 1
|
||||||
|
- Phase 8 complete — all three cron jobs registered (daily report, weekly summary, weekly archive)
|
||||||
|
- User should verify first archive dry-run output and set `DRY_RUN=false` in `archive-stale-sessions.sh` to enable pruning
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 08-cron-reporting*
|
||||||
|
*Completed: 2026-06-15*
|
||||||
135
.planning/phases/08-cron-reporting/08-CONTEXT.md
Normal file
135
.planning/phases/08-cron-reporting/08-CONTEXT.md
Normal file
@@ -0,0 +1,135 @@
|
|||||||
|
# Phase 8: Cron Reporting - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-06-14
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
Set up daily operational reporting and weekly stale session lifecycle management using Hermes' built-in cron and session management capabilities.
|
||||||
|
|
||||||
|
**In scope:** Daily report cron job via Telegram, Jira progress updates for active sessions, weekly stale session summary, weekly stale session archive using `hermes sessions export` + `hermes sessions prune`
|
||||||
|
|
||||||
|
**Out of scope:** Session lifecycle skill (Phase 7 already done), tool provisioning (Phase 9), portable setup script (Phase 9), auto-closing Jira tickets (user must specify), modifying Jira ticket statuses without user instruction
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### Report Schedule
|
||||||
|
- **D-01:** Daily report runs at **09:00 SGT (UTC+8)** — Hermes cron job using `hermes cron create`
|
||||||
|
- **D-02:** Weekly stale session summary runs **Sunday 20:00 SGT (UTC+8)** — separate cron job
|
||||||
|
- **D-03:** Weekly stale session archive runs **Sunday 20:00 SGT (UTC+8)** — same cron job as summary (archive after summary)
|
||||||
|
|
||||||
|
### Daily Report Content
|
||||||
|
- **D-04:** Daily report updates **all Jira tickets** linked to active sessions with last activity and progress (session → Jira is 1-to-many mapping)
|
||||||
|
- **D-05:** Do **NOT** update Jira ticket statuses — only add comments with session progress/state
|
||||||
|
- **D-06:** Daily report delivered via Telegram to user's DM channel
|
||||||
|
- **D-07:** Report includes: list of active sessions, last activity timestamp, Jira ticket keys updated
|
||||||
|
|
||||||
|
### Weekly Stale Report
|
||||||
|
- **D-08:** Weekly report summarizes all inactive sessions (>30d since last activity) — session title, last activity date, linked Jira ticket
|
||||||
|
- **D-09:** Delivered via Telegram to user's DM channel
|
||||||
|
- **D-10:** No auto-comments on stale tickets — only reported in the summary
|
||||||
|
|
||||||
|
### Session Archive
|
||||||
|
- **D-11:** Use native Hermes commands — no custom scripts:
|
||||||
|
- **Export:** `hermes sessions export --older-than 30d --output ~/.hermes/archive/sessions/sessions-$(date +%Y%m%d).jsonl`
|
||||||
|
- **Prune:** `hermes sessions prune --older-than 30d --confirm` (after export verification)
|
||||||
|
- **D-12:** Archive location: `~/.hermes/archive/sessions/`
|
||||||
|
- **D-13:** Run weekly Sunday 20:00 SGT, after the stale summary report
|
||||||
|
|
||||||
|
### Jira Integration
|
||||||
|
- **D-14:** Active sessions with linked Jira tickets get daily progress comments added automatically
|
||||||
|
- **D-15:** Stale sessions with Jira tickets are mentioned in the weekly summary only (no auto-comment)
|
||||||
|
- **D-16:** Ticket status transitions only happen when user explicitly asks
|
||||||
|
|
||||||
|
### Cron Job Mechanism
|
||||||
|
- **D-17:** Both cron jobs use Hermes skill-backed cron (`hermes cron create` with a skill that defines the report behavior)
|
||||||
|
- **D-18:** The daily report skill references the existing session skill (Phase 7) and ngn-jira skill (Phase 4) for operations
|
||||||
|
- **D-19:** The stale archive step uses `no_agent: true` mode (deterministic CLI commands, no LLM cost)
|
||||||
|
|
||||||
|
### Technical Corrections from Research
|
||||||
|
|
||||||
|
- **`hermes sessions export`** has NO `--older-than` flag — exports ALL sessions, filters by `--source` or `--session-id` only. Archive script must export all then `prune --older-than 30 --yes` to clean up.
|
||||||
|
- **`hermes sessions prune`** uses `--yes` (not `--confirm`)
|
||||||
|
- **`hermes sessions list`** has NO `--json` flag
|
||||||
|
- **Jira-session mapping** lives in hindsight memory (saved by Phase 7 session skill), not in the session DB — cron agent must use `hindsight_recall` to discover which Jira tickets belong to which sessions
|
||||||
|
- **System timezone already SGT (+08)** — no TZ configuration needed
|
||||||
|
- **Three cron jobs required**: Daily report (09:00 SGT), weekly stale summary (Sunday 20:00 SGT), weekly archive (Sunday 20:05 SGT — 5 min after summary to avoid race conditions)
|
||||||
|
|
||||||
|
### the agent's Discretion
|
||||||
|
- **Report formatting**: Telegram message structure — planner designs based on 4096-char limit
|
||||||
|
- **Cron job naming**: Standard naming convention for the three cron jobs
|
||||||
|
- **Dry-run phase**: First archive via `hermes sessions export sessions.jsonl` then review JSONL before enabling prune
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
**Downstream agents MUST read these before planning or implementing.**
|
||||||
|
|
||||||
|
### Hermes Documentation
|
||||||
|
- `~/.hermes/config.yaml` §`cron:` — Existing cron configuration (wrap_response, max_parallel_jobs)
|
||||||
|
- `~/.hermes/config.yaml` §`TELEGRAM_HOME_CHANNEL` — Delivery channel for cron output
|
||||||
|
- `hermes cron create --help` — Cron job creation syntax
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
- `hermes sessions export --help` — Session export to JSONL
|
||||||
|
- `hermes sessions prune --help` — Session pruning
|
||||||
|
- `hermes sessions list --help` — Session listing
|
||||||
|
|
||||||
|
### Completed Phases (Dependencies)
|
||||||
|
- `.planning/phases/05-hindsight-memory-provider/05-01-SUMMARY.md` — Hindsight active for cross-session insights
|
||||||
|
- `.planning/phases/07-main-session-skill/07-01-SUMMARY.md` — Session lifecycle skill creates structured sessions with Jira links
|
||||||
|
|
||||||
|
### Project Documents
|
||||||
|
- `initial-plan.md` §`func daily_report()` — Original reporting spec
|
||||||
|
- `.planning/REQUIREMENTS.md` §CRON-01, CRON-02, CRON-03 — Requirement definitions
|
||||||
|
- `.planning/ROADMAP.md` §Phase 8 — Phase goal and success criteria
|
||||||
|
|
||||||
|
### Existing Skills (Referenced by cron skills)
|
||||||
|
- `~/.hermes/skills/ngn-agent/session/SKILL.md` — Session lifecycle skill
|
||||||
|
- `~/.hermes/skills/ngn-agent/jira/SKILL.md` — Jira query skill
|
||||||
|
</canonical_refs>
|
||||||
|
|
||||||
|
<code_context>
|
||||||
|
## Existing Code Insights
|
||||||
|
|
||||||
|
### Reusable Assets
|
||||||
|
- **Hermes cron system**: Fully operational in gateway — `hermes cron create` with skill-backed or no_agent jobs
|
||||||
|
- **Telegram delivery**: `TELEGRAM_HOME_CHANNEL` already set to user's DM (474440517)
|
||||||
|
- **Session DB**: SQLite-based, queryable via `hermes sessions list --json`
|
||||||
|
- **ngn-jira script**: Available in Docker for Jira operations
|
||||||
|
|
||||||
|
### Established Patterns
|
||||||
|
- **Skill-backed cron**: Cron job that loads a SKILL.md and lets the agent compose the report
|
||||||
|
- **no_agent cron**: Deterministic commands without LLM involvement (for archive step)
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- `hermes cron create` → Register daily report job
|
||||||
|
- `hermes cron create --no-agent` → Register archive job
|
||||||
|
- `~/.hermes/archive/sessions/` → Archive storage directory (create)
|
||||||
|
</code_context>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- The daily report should be a Hermes skill that instructs the agent to query session DB for active sessions, then update each session's Jira ticket with a progress comment, then compose a Telegram summary
|
||||||
|
- The stale archive should be a no_agent cron: `hermes sessions export ... && hermes sessions prune ...`
|
||||||
|
- Start with dry-run on archive: run export manually first, verify JSONL, then enable the prune
|
||||||
|
- SGT (UTC+8) timezone should be set explicitly in the cron job or system config
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<deferred>
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Auto-close tickets for archived sessions — user wants manual control
|
||||||
|
- Teams gateway delivery — only Telegram for now
|
||||||
|
- Custom archive dashboard — JSONL files are searchable via grep/FTS5
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 8-Cron Reporting*
|
||||||
|
*Context gathered: 2026-06-14*
|
||||||
47
.planning/phases/08-cron-reporting/08-DISCUSSION-LOG.md
Normal file
47
.planning/phases/08-cron-reporting/08-DISCUSSION-LOG.md
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# Phase 8: Cron Reporting - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.** Do not use as input to planning, research, or execution agents.
|
||||||
|
|
||||||
|
**Date:** 2026-06-14
|
||||||
|
**Phase:** 8-Cron Reporting
|
||||||
|
**Areas discussed:** Report schedule, Report content, Archive behavior, Jira integration depth
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Report Schedule
|
||||||
|
|
||||||
|
**User's choice:** Daily at 09:00 SGT (UTC+8). Weekly Sunday 20:00 SGT for stale summary + archive.
|
||||||
|
|
||||||
|
**Notes:** Two separate cron jobs — daily report and weekly stale/archive.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Report Content
|
||||||
|
|
||||||
|
**User's choice:** Daily report updates JIRA with last activity (session → JIRA is 1-to-many). No ticket status updates unless user specifies. Weekly stale report summarizes all inactive sessions.
|
||||||
|
|
||||||
|
**Notes:** Daily = active session progress comments on linked JIRAs. Weekly = inactive session summary only.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Archive Behavior
|
||||||
|
|
||||||
|
**User's choice:** Use Hermes-native commands (`hermes sessions export` + `hermes sessions prune`).
|
||||||
|
|
||||||
|
**Notes:** Native Hermes tools exist — no custom scripting needed. Archive to `~/.hermes/archive/sessions/`. Dry-run mode first.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Jira Integration Depth
|
||||||
|
|
||||||
|
**User's choice:** Active tickets get daily progress comments. Stale tickets get mentioned in summary only (no auto-comment). No auto status transitions.
|
||||||
|
|
||||||
|
**Notes:** User must explicitly ask for ticket status changes. 1-to-many session→Jira mapping.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Auto-close tickets for archived sessions
|
||||||
|
- Teams gateway delivery
|
||||||
|
- Custom archive dashboard
|
||||||
603
.planning/phases/08-cron-reporting/08-RESEARCH.md
Normal file
603
.planning/phases/08-cron-reporting/08-RESEARCH.md
Normal file
@@ -0,0 +1,603 @@
|
|||||||
|
# Phase 8: Cron Reporting - Research
|
||||||
|
|
||||||
|
**Researched:** 2026-06-15
|
||||||
|
**Domain:** Hermes cron scheduling, session lifecycle management, Telegram delivery, Jira integration via cron
|
||||||
|
**Confidence:** HIGH
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Phase 8 implements daily operational reporting and weekly stale session lifecycle management using Hermes' built-in cron and session management capabilities. Three cron jobs are needed: a daily report (skill-backed), a weekly stale summary (skill-backed), and a weekly archive (no_agent). All use `hermes cron create` with appropriate flags.
|
||||||
|
|
||||||
|
**Critical finding:** The CONTEXT.md assumptions about `hermes sessions export` flags are incorrect — `--older-than` and `--output` do not exist on the export command. The export takes a positional output path and supports only `--source` and `--session-id` filters. The archive script must compensate by exporting all sessions and relying on the separate `prune --older-than 30` step to remove old ones.
|
||||||
|
|
||||||
|
**Primary recommendation:** Two skill-backed cron jobs (daily report 09:00 SGT, weekly summary Sunday 20:00 SGT) plus one no_agent script cron (archive Sunday 20:05 SGT). The Jira-session mapping is not a direct DB field — the skill-backed cron agents use `hindsight_recall` or `session_search` to discover ticket keys from session summaries stored by Phase 7's session skill.
|
||||||
|
|
||||||
|
<user_constraints>
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
### Locked Decisions
|
||||||
|
- **D-01:** Daily report runs at 09:00 SGT (UTC+8) — Hermes cron job using `hermes cron create`
|
||||||
|
- **D-02:** Weekly stale session summary runs Sunday 20:00 SGT (UTC+8) — separate cron job
|
||||||
|
- **D-03:** Weekly stale session archive runs Sunday 20:00 SGT (UTC+8) — same cron job as summary (archive after summary)
|
||||||
|
- **D-04:** Daily report updates all Jira tickets linked to active sessions with last activity and progress
|
||||||
|
- **D-05:** Do NOT update Jira ticket statuses — only add comments with session progress/state
|
||||||
|
- **D-06:** Daily report delivered via Telegram to user's DM channel
|
||||||
|
- **D-07:** Report includes: list of active sessions, last activity timestamp, Jira ticket keys updated
|
||||||
|
- **D-08:** Weekly report summarizes all inactive sessions (>30d since last activity)
|
||||||
|
- **D-09:** Delivered via Telegram to user's DM channel
|
||||||
|
- **D-10:** No auto-comments on stale tickets — only reported in the summary
|
||||||
|
- **D-11:** Use native Hermes commands for archive (export + prune) — no custom scripts for core logic
|
||||||
|
- **D-12:** Archive location: `~/.hermes/archive/sessions/`
|
||||||
|
- **D-13:** Run weekly Sunday 20:00 SGT, after the stale summary report
|
||||||
|
- **D-14:** Active sessions with linked Jira tickets get daily progress comments
|
||||||
|
- **D-15:** Stale sessions with Jira tickets are mentioned in the weekly summary only
|
||||||
|
- **D-16:** Ticket status transitions only happen when user explicitly asks
|
||||||
|
- **D-17:** Both cron jobs use Hermes skill-backed cron
|
||||||
|
- **D-18:** The daily report skill references the existing session skill and ngn-jira skill
|
||||||
|
- **D-19:** The stale archive step uses `no_agent: true` mode
|
||||||
|
|
||||||
|
### the agent's Discretion
|
||||||
|
- Report formatting: Telegram message structure (sections, bullet points) — planner can design based on Telegram's 4096-char limit
|
||||||
|
- Cron job naming: Standard naming convention for the two cron jobs
|
||||||
|
- Dry-run phase: First archive run should be manual (export then review JSONL before enabling prune)
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
- Auto-close tickets for archived sessions — user wants manual control
|
||||||
|
- Teams gateway delivery — only Telegram for now
|
||||||
|
- Custom archive dashboard — JSONL files are searchable via grep/FTS5
|
||||||
|
</user_constraints>
|
||||||
|
|
||||||
|
<phase_requirements>
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|------------------|
|
||||||
|
| CRON-01 | Daily session summary report delivered via Telegram at 09:00 | `hermes cron create --deliver telegram --skill session --skill jira-query "0 9 * * *"` — verified working. Daily report prompt guides agent to list active sessions, query Jira, compose Telegram message. |
|
||||||
|
| CRON-02 | Stale session auto-archive (30d inactivity) — export to JSON, delete from live DB | `hermes sessions export <path>` + `hermes sessions prune --older-than 30 --yes`. Export lacks `--older-than` filter, so archive script exports all sessions. Prune correctly filters by age. Verified working. |
|
||||||
|
| CRON-03 | Daily report includes Jira ticket status via ngn-jira skill | `ngn-jira` script available at `~/.hermes/scripts/ngn-jira`. Jira-session mapping lives in hindsight memory (session summaries) and session message content, not as a direct DB field. |
|
||||||
|
</phase_requirements>
|
||||||
|
|
||||||
|
## Architectural Responsibility Map
|
||||||
|
|
||||||
|
| Capability | Primary Tier | Secondary Tier | Rationale |
|
||||||
|
|------------|-------------|----------------|-----------|
|
||||||
|
| Daily report scheduling | Cron scheduler (Hermes gateway) | — | `hermes cron create` registers the job; Hermes gateway process fires it on schedule |
|
||||||
|
| Active session discovery | Cron agent (LLM) | Session DB (SQLite) | Skill-backed cron agent runs `hermes sessions list` or `hermes sessions export -` to enumerate sessions and determine active/stale via `last_active` timestamps |
|
||||||
|
| Jira comment posting | Cron agent (LLM) | `ngn-jira` script | Agent crafts `ngn-jira POST '/rest/api/3/issue/<KEY>/comment'` for each active session with a Jira link |
|
||||||
|
| Telegram message delivery | Hermes cron delivery system | — | `--deliver telegram` sends cron agent's response to TELEGRAM_HOME_CHANNEL (474440517) |
|
||||||
|
| Stale session archive | no_agent script (bash) | Hermes CLI | `--no-agent` mode runs `~/.hermes/scripts/archive-stale-sessions.sh` which calls `hermes sessions export` + `hermes sessions prune` directly |
|
||||||
|
| Jira-session mapping lookup | Hindsight memory / session_search | Session message content | No direct `jira_key` field in session DB; mapping is discovered through hindsight recall of session summaries or searching session messages |
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core
|
||||||
|
| Library/Tool | Version | Purpose | Why Standard |
|
||||||
|
|-------------|---------|---------|--------------|
|
||||||
|
| `hermes cron create` | v0.16.0 | Register scheduled cron jobs | Built-in — no external scheduler needed |
|
||||||
|
| `hermes sessions export` | v0.16.0 | Export sessions to JSONL backup | Native Hermes command, writes full session data (messages included) |
|
||||||
|
| `hermes sessions prune` | v0.16.0 | Delete old sessions from live DB | Native Hermes command with `--older-than` and `--yes` flags |
|
||||||
|
| `hermes sessions list` | v0.16.0 | List recent sessions with last active times | Native Hermes command, human-readable table output |
|
||||||
|
| `ngn-jira` script | — | Jira Cloud API wrapper (GET/POST/PUT/DELETE) | Already exists in `~/.hermes/scripts/`, used by Phase 7 session skill |
|
||||||
|
|
||||||
|
### Supporting
|
||||||
|
| Library/Tool | Version | Purpose | When to Use |
|
||||||
|
|-------------|---------|---------|-------------|
|
||||||
|
| `session` skill (Phase 7) | v1.0.0 | Session lifecycle definitions and hindsight session summaries | Daily and weekly cron jobs load this to understand session structure and Jira mapping |
|
||||||
|
| `jira-query` skill (Phase 4) | v1.0.0 | Jira API query patterns | Daily report cron job loads this for Jira comment operations |
|
||||||
|
| `bash` (no_agent script) | system | Deterministic archive operations without LLM cost | Archive step only — pure CLI, no LLM involvement |
|
||||||
|
| `jq` (optional) | system | Filter JSONL for precise archive content | Optional — only needed if archive should contain only stale sessions (avoids dependency) |
|
||||||
|
| `date` command | system | Date-stamped archive filenames | `$(date +%Y%m%d)` for `sessions-20260615.jsonl` naming |
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Instead of | Could Use | Tradeoff |
|
||||||
|
|------------|-----------|----------|
|
||||||
|
| Skill-backed cron | `--script` + `--no-agent` for daily report | Need LLM for composing natural-language reports and determining active vs stale from context |
|
||||||
|
| Separate cron jobs | Single cron with complex script | Cleaner separation of concerns — daily vs weekly have different scopes |
|
||||||
|
| Custom Jira field for session-ID | Hindsight + session_search lookup | Adding custom Jira fields requires admin access and changes existing workflow |
|
||||||
|
|
||||||
|
**Installation:**
|
||||||
|
```bash
|
||||||
|
# No new packages needed — all tools are native Hermes CLI or existing scripts
|
||||||
|
# Create archive directory:
|
||||||
|
mkdir -p ~/.hermes/archive/sessions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Version verification:**
|
||||||
|
```bash
|
||||||
|
hermes --version
|
||||||
|
# Hermes Agent v0.16.0 (2026.6.5) · upstream 78c11d99
|
||||||
|
|
||||||
|
# Verify ngn-jira script exists:
|
||||||
|
file ~/.hermes/scripts/ngn-jira
|
||||||
|
# Bourne-Again shell script, ASCII text
|
||||||
|
|
||||||
|
# Verify hermes sessions commands available:
|
||||||
|
hermes sessions --help
|
||||||
|
hermes cron --help
|
||||||
|
```
|
||||||
|
|
||||||
|
## Package Legitimacy Audit
|
||||||
|
|
||||||
|
> No external packages are installed in this phase. All dependencies are native Hermes CLI commands and existing scripts.
|
||||||
|
|
||||||
|
| Package | Registry | Age | Downloads | Source Repo | Verdict | Disposition |
|
||||||
|
|---------|----------|-----|-----------|-------------|---------|-------------|
|
||||||
|
| (none) | — | — | — | — | — | No packages to audit |
|
||||||
|
|
||||||
|
**Packages removed due to [SLOP] verdict:** none
|
||||||
|
**Packages flagged as suspicious [SUS]:** none
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### System Architecture Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────────┐
|
||||||
|
│ HERMES GATEWAY PROCESS │
|
||||||
|
│ (cron scheduler — checks every minute, fires due jobs) │
|
||||||
|
└──────┬────────────────────────────────┬───────────────────────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌────────────────┐ ┌──────────────────────────┐
|
||||||
|
│ DAILY REPORT │ │ WEEKLY SUNDAY 20:00 SGT │
|
||||||
|
│ 09:00 SGT │ │ │
|
||||||
|
│ skill-backed │ ├─────────────────────┬────┤
|
||||||
|
│ with session + │ │ Weekly Stale │ │
|
||||||
|
│ jira-query │ │ Summary │ │
|
||||||
|
│ skills │ │ (skill-backed) │ │
|
||||||
|
└────────┬───────┘ └──────────┬────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌─────────────────┐ ┌────────────────┐ ┌─────────────────┐
|
||||||
|
│ Agent loads │ │ Agent queries │ │ no_agent script │
|
||||||
|
│ skills, runs: │ │ sessions list, │ │ runs: │
|
||||||
|
│ │ │ finds stale >30d│ │ │
|
||||||
|
│ 1. hermes │ │ identifies Jira │ │ 1. hermes │
|
||||||
|
│ sessions list │ │ tickets (via │ │ sessions │
|
||||||
|
│ → find active │ │ hindsight) │ │ export │
|
||||||
|
│ sessions │ │ composes stale │ │ → archive │
|
||||||
|
│ │ │ summary report │ │ │
|
||||||
|
│ 2. hindsight │ │ sends Telegram │ │ 2. hermes │
|
||||||
|
│ _recall → │ │ DM │ │ sessions │
|
||||||
|
│ find Jira keys│ └────────────────┘ │ prune │
|
||||||
|
│ for each │ │ --older-than │
|
||||||
|
│ active session│ │ 30 --yes │
|
||||||
|
│ │ └────────┬────────┘
|
||||||
|
│ 3. ngn-jira POST │ │
|
||||||
|
│ → add progress │ ▼
|
||||||
|
│ comment on │ ┌─────────────────┐
|
||||||
|
│ each Jira │ │ ~/.hermes/archive│
|
||||||
|
│ ticket │ │ /sessions/ │
|
||||||
|
│ │ │ sessions- │
|
||||||
|
│ 4. sends │ │ 20260615.jsonl │
|
||||||
|
│ Telegram DM │ └─────────────────┘
|
||||||
|
│ summary │
|
||||||
|
└─────────────────┘
|
||||||
|
|
||||||
|
TELEGRAM_HOME_CHANNEL (474440517)
|
||||||
|
▲ ▲
|
||||||
|
│ │
|
||||||
|
Daily Report Weekly Summary
|
||||||
|
│ │
|
||||||
|
┌─────┴──────────────┴──────┐
|
||||||
|
│ USER's TELEGRAM DM │
|
||||||
|
└────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended Project Structure
|
||||||
|
|
||||||
|
No new project files needed — all configuration is in `~/.hermes/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.hermes/
|
||||||
|
├── config.yaml # Already configured: TELEGRAM_HOME_CHANNEL, cron section
|
||||||
|
├── scripts/
|
||||||
|
│ ├── ngn-jira # Existing — Jira Cloud API wrapper
|
||||||
|
│ ├── ngn-confluence # Existing
|
||||||
|
│ ├── ngn-bitbucket # Existing
|
||||||
|
│ ├── session-init.sh # Existing
|
||||||
|
│ └── archive-stale-sessions.sh # NEW — no_agent archive script
|
||||||
|
├── cron/
|
||||||
|
│ └── output/ # Cron output logs (auto-managed by Hermes)
|
||||||
|
└── archive/
|
||||||
|
└── sessions/ # NEW — stale session JSONL archives
|
||||||
|
└── sessions-20260615.jsonl # Named by date
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 1: Skill-Backed Cron Job Registration
|
||||||
|
|
||||||
|
**What:** Register a cron job that loads one or more skills and instructs an LLM agent to perform a task on a schedule.
|
||||||
|
|
||||||
|
**When to use:** Any recurring task that requires natural language composition, decision-making, or tool orchestration.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Register daily report (skill-backed)
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-daily-report" \
|
||||||
|
--deliver telegram \
|
||||||
|
--skill session \
|
||||||
|
--skill jira-query \
|
||||||
|
"0 9 * * *" \
|
||||||
|
"Daily operational report: list all active sessions with their last activity timestamps using hermes sessions list, for each active session find its linked Jira ticket via hindsight_recall or message search, then add a progress comment via ngn-jira POST /rest/api/3/issue/<KEY>/comment, and finally compose a Telegram summary of all updated tickets and active sessions."
|
||||||
|
|
||||||
|
# Register weekly stale summary (skill-backed)
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-weekly-stale-summary" \
|
||||||
|
--deliver telegram \
|
||||||
|
--skill session \
|
||||||
|
"0 20 * * 0" \
|
||||||
|
"Weekly stale session summary: run hermes sessions list to find all sessions, identify sessions where last activity is more than 30 days ago (stale), for each stale session find its linked Jira ticket via hindsight_recall, compose a summary report with session title, last activity date, and linked Jira ticket. Do NOT add Jira comments — only report in the summary. Send via Telegram."
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verification:**
|
||||||
|
```bash
|
||||||
|
# List registered cron jobs
|
||||||
|
hermes cron list
|
||||||
|
|
||||||
|
# Test-run a cron job
|
||||||
|
hermes cron run ngn-daily-report
|
||||||
|
|
||||||
|
# Check cron scheduler status
|
||||||
|
hermes cron status
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: no_agent Deterministic Cron Script
|
||||||
|
|
||||||
|
**What:** Register a cron job that runs a shell script without LLM involvement, delivering its stdout verbatim to the target.
|
||||||
|
|
||||||
|
**When to use:** Deterministic CLI operations that don't need AI reasoning (backup, cleanup, export).
|
||||||
|
|
||||||
|
**Example script (`~/.hermes/scripts/archive-stale-sessions.sh`):**
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Archive stale sessions (inactive >30 days) and prune from live DB
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ARCHIVE_DIR="$HOME/.hermes/archive/sessions"
|
||||||
|
mkdir -p "$ARCHIVE_DIR"
|
||||||
|
OUTPUT_FILE="$ARCHIVE_DIR/sessions-$(date +%Y%m%d).jsonl"
|
||||||
|
|
||||||
|
echo "=== Stale Session Archive: $(date) ==="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "1. Exporting all sessions to $OUTPUT_FILE ..."
|
||||||
|
hermes sessions export "$OUTPUT_FILE"
|
||||||
|
export_exit=$?
|
||||||
|
echo " Export exit code: $export_exit"
|
||||||
|
echo " File size: $(wc -c < "$OUTPUT_FILE") bytes"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "2. Pruning sessions older than 30 days from live DB ..."
|
||||||
|
hermes sessions prune --older-than 30 --yes
|
||||||
|
prune_exit=$?
|
||||||
|
echo " Prune exit code: $prune_exit"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "3. Session store stats after archive:"
|
||||||
|
hermes sessions stats
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
if [ $export_exit -eq 0 ] && [ $prune_exit -eq 0 ]; then
|
||||||
|
echo "Archive completed successfully."
|
||||||
|
else
|
||||||
|
echo "Archive completed with warnings (see exit codes above)."
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Registration:**
|
||||||
|
```bash
|
||||||
|
# Register weekly archive (no_agent, 5 min after stale summary to avoid race)
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-weekly-archive" \
|
||||||
|
--deliver telegram \
|
||||||
|
--script archive-stale-sessions.sh \
|
||||||
|
--no-agent \
|
||||||
|
"5 20 * * 0"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
- **Race condition on same schedule:** If weekly summary and archive run at exactly the same time, the prune might remove sessions while the summary is still being composed. Offset by at least 5 minutes (e.g., 20:00 summary, 20:05 archive).
|
||||||
|
- **Custom cron daemon:** Hermes already has a full cron implementation — no need for system cron, launchd, or external schedulers.
|
||||||
|
- **Storing Jira key as session metadata:** The session DB has no `jira_key` field. Attempting to add one would require modifying Hermes internals. Use hindsight memory for the mapping (as designed by Phase 7).
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| Scheduling recurring tasks | Custom cron/launchd/service | `hermes cron create` | Hermes gateway manages scheduling, delivery, retries, and logging natively |
|
||||||
|
| Session backup/export | Custom SQLite query tool | `hermes sessions export` | Native JSONL export includes complete session data (messages, tokens, costs) |
|
||||||
|
| Session cleanup | Custom deletion script | `hermes sessions prune` | Handles DB vacuuming and FTS index maintenance after deletion |
|
||||||
|
| Telegram message delivery | Custom Telegram bot | `--deliver telegram` | Uses existing TELEGRAM_HOME_CHANNEL config and channel_directory |
|
||||||
|
| Jira API integration | Custom HTTP calls | `ngn-jira` script | Existing script handles auth, base URL, error handling |
|
||||||
|
|
||||||
|
**Key insight:** Every tool needed for this phase already exists in the Hermes ecosystem. The phase is about composing them through cron job registration, not building new components. The only new file is the `archive-stale-sessions.sh` script.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Export lacks `--older-than` filter
|
||||||
|
**What goes wrong:** CONTEXT.md specifies `hermes sessions export --older-than 30d --output <path>` but this flag does not exist.
|
||||||
|
**Why it happens:** The export command only supports `--source` and `--session-id` filters.
|
||||||
|
**How to avoid:** The no_agent archive script exports ALL sessions to the archive file, then the separate `hermes sessions prune --older-than 30 --yes` step removes only old ones from the live DB. This is safe — the archive is a snapshot and extra data in it is harmless.
|
||||||
|
**Warning signs:** `hermes: error: unrecognized arguments: --older-than`
|
||||||
|
|
||||||
|
### Pitfall 2: Prune uses `--yes` not `--confirm`
|
||||||
|
**What goes wrong:** CONTEXT.md specifies `hermes sessions prune --older-than 30d --confirm` but the correct flag is `--yes` (or `-y`).
|
||||||
|
**How to avoid:** Use `hermes sessions prune --older-than 30 --yes` in the no_agent script. Without `--yes`, the command prompts for confirmation and blocks in no_agent mode.
|
||||||
|
**Warning signs:** Cron job hangs waiting for confirmation input.
|
||||||
|
|
||||||
|
### Pitfall 3: `hermes sessions list` does NOT support `--json`
|
||||||
|
**What goes wrong:** The agent expects JSON output to parse session data programmatically.
|
||||||
|
**How to avoid:** For the skill-backed cron, the agent runs `hermes sessions list` and parses the human-readable table. Alternatively, use `hermes sessions export -` which outputs JSONL and can be parsed. The export includes all session metadata including `last_active`, `source`, `title`, `message_count`.
|
||||||
|
**Warning signs:** `hermes: error: unrecognized arguments: --json`
|
||||||
|
|
||||||
|
### Pitfall 4: Jira-session mapping not in session DB
|
||||||
|
**What goes wrong:** The cron agent cannot find which Jira tickets belong to which sessions by querying the session DB.
|
||||||
|
**Why it happens:** Hermes session DB has no `jira_key` field. The mapping is recorded only in hindsight memory (session summaries saved by Phase 7's session skill) and in session message content.
|
||||||
|
**How to avoid:** The skill-backed cron agents should use `hindsight_recall` with query "session summary" to find Jira ticket keys, or use `session_search` tool to search session transcripts for ticket key patterns like `PLATFORM-123`.
|
||||||
|
**Warning signs:** Empty Jira update section in daily report.
|
||||||
|
|
||||||
|
### Pitfall 5: Cron schedule interpretation (UTC vs local)
|
||||||
|
**What goes wrong:** Cron expression `0 9 * * *` might be interpreted as UTC if the system timezone is not configured.
|
||||||
|
**Why it happens:** Hermes cron uses system timezone by default. With `timezone: ''` in config, it falls back to system default.
|
||||||
|
**How to avoid:** System already in SGT (UTC+8, confirmed via `date +%Z` → `+08`). The created test cron showed "Next run: 2026-06-16T09:00:00+08:00" confirming correct timezone handling. No TZ configuration needed.
|
||||||
|
**Warning signs:** Cron fires at wrong hour (e.g., 01:00 UTC instead of 09:00 SGT).
|
||||||
|
|
||||||
|
### Pitfall 6: Cron job naming — must use unique name
|
||||||
|
**What goes wrong:** Hermes cron jobs are identified by name or ID. Using duplicate names may cause confusion.
|
||||||
|
**How to avoid:** Use descriptive unique names: `ngn-daily-report`, `ngn-weekly-stale-summary`, `ngn-weekly-archive`.
|
||||||
|
**Warning signs:** `hermes cron list` shows unexpected jobs.
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### Command 1: Create the daily report cron job
|
||||||
|
```bash
|
||||||
|
# Daily at 09:00 SGT — report on active sessions, update Jira tickets
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-daily-report" \
|
||||||
|
--deliver telegram \
|
||||||
|
--skill session \
|
||||||
|
--skill jira-query \
|
||||||
|
"0 9 * * *" \
|
||||||
|
"Daily operational report: run hermes sessions list to find all active sessions (updated today or yesterday), for each active session search for its linked Jira ticket key using hindsight_recall with query 'session summary jira' or message search, then add a progress comment to each Jira ticket via ngn-jira POST /rest/api/3/issue/<KEY>/comment with the last activity timestamp and session summary, and finally compose a Telegram summary listing: active sessions with last activity, Jira ticket keys updated, and session titles."
|
||||||
|
```
|
||||||
|
**Verified:** `hermes cron create --deliver telegram` successfully creates jobs with correct timezone handling. [VERIFIED: Hermes CLI v0.16.0]
|
||||||
|
|
||||||
|
### Command 2: Create the weekly stale summary cron job
|
||||||
|
```bash
|
||||||
|
# Sunday 20:00 SGT — stale session summary (no Jira comments)
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-weekly-stale-summary" \
|
||||||
|
--deliver telegram \
|
||||||
|
--skill session \
|
||||||
|
"0 20 * * 0" \
|
||||||
|
"Weekly stale session summary: run hermes sessions list to find all sessions where last activity is more than 30 days ago. For each stale session, find the linked Jira ticket key via hindsight_recall if available. Compose a Telegram summary listing: session title, last activity date, linked Jira ticket. Do NOT add comments to Jira tickets — only report in the summary."
|
||||||
|
```
|
||||||
|
**Verified:** Cron schedule `0 20 * * 0` = Sunday at 20:00. [VERIFIED: conventional cron expression]
|
||||||
|
|
||||||
|
### Command 3: Create the weekly archive cron job (no_agent)
|
||||||
|
```bash
|
||||||
|
# Create archive directory first
|
||||||
|
mkdir -p ~/.hermes/archive/sessions
|
||||||
|
|
||||||
|
# Register no_agent archive (5 min after summary to avoid race condition)
|
||||||
|
hermes cron create \
|
||||||
|
--name "ngn-weekly-archive" \
|
||||||
|
--deliver telegram \
|
||||||
|
--script archive-stale-sessions.sh \
|
||||||
|
--no-agent \
|
||||||
|
"5 20 * * 0"
|
||||||
|
```
|
||||||
|
**Verified:** `--no-agent` flag skips LLM entirely. Script must be under `~/.hermes/scripts/`. Stdout delivered verbatim. [VERIFIED: Hermes CLI v0.16.0]
|
||||||
|
|
||||||
|
### Command 4: Create the archive script
|
||||||
|
**File:** `~/.hermes/scripts/archive-stale-sessions.sh`
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Archive stale sessions (inactive >30 days) and prune from live DB
|
||||||
|
# This script runs weekly via hermes cron with --no-agent
|
||||||
|
# Stdout is delivered to Telegram via --deliver telegram
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
ARCHIVE_DIR="$HOME/.hermes/archive/sessions"
|
||||||
|
mkdir -p "$ARCHIVE_DIR"
|
||||||
|
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
|
||||||
|
OUTPUT_FILE="$ARCHIVE_DIR/sessions-${TIMESTAMP}.jsonl"
|
||||||
|
|
||||||
|
echo "=== Stale Session Archive ==="
|
||||||
|
echo "Started: $(date)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "[1/3] Exporting session store..."
|
||||||
|
hermes sessions export "$OUTPUT_FILE"
|
||||||
|
echo " -> $(wc -l < "$OUTPUT_FILE") sessions exported"
|
||||||
|
echo " -> Size: $(du -h "$OUTPUT_FILE" | cut -f1)"
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "[2/3] Pruning sessions older than 30 days..."
|
||||||
|
hermes sessions prune --older-than 30 --yes
|
||||||
|
echo " Done."
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "[3/3] Post-archive stats:"
|
||||||
|
hermes sessions stats
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
echo "✓ Archive complete."
|
||||||
|
```
|
||||||
|
**Verified:** `--older-than 30` takes integer days (not `30d`), `--yes` skips interactive confirmation. [VERIFIED: Hermes CLI v0.16.0]
|
||||||
|
|
||||||
|
### Command 5: List, test-run, and manage cron jobs
|
||||||
|
```bash
|
||||||
|
# List all cron jobs
|
||||||
|
hermes cron list
|
||||||
|
|
||||||
|
# Test-run a specific job (fires immediately)
|
||||||
|
hermes cron run ngn-daily-report
|
||||||
|
|
||||||
|
# Check if gateway/cron scheduler is running
|
||||||
|
hermes cron status
|
||||||
|
# → "Gateway is running — cron jobs will fire automatically"
|
||||||
|
|
||||||
|
# Remove a cron job (if needed)
|
||||||
|
hermes cron remove ngn-daily-report
|
||||||
|
|
||||||
|
# Edit an existing cron job (add/remove skills, change schedule)
|
||||||
|
hermes cron edit ngn-daily-report --add-skill jira-query
|
||||||
|
hermes cron edit ngn-daily-report --schedule "30 9 * * *"
|
||||||
|
```
|
||||||
|
**Verified:** All commands tested against Hermes CLI v0.16.0. [VERIFIED: Hermes CLI v0.16.0]
|
||||||
|
|
||||||
|
### Command 6: Session management operations
|
||||||
|
```bash
|
||||||
|
# List all sessions
|
||||||
|
hermes sessions list
|
||||||
|
|
||||||
|
# Export all sessions to stdout (JSONL format)
|
||||||
|
hermes sessions export -
|
||||||
|
|
||||||
|
# Export to file
|
||||||
|
hermes sessions export /tmp/my-sessions.jsonl
|
||||||
|
|
||||||
|
# Show session store statistics
|
||||||
|
hermes sessions stats
|
||||||
|
|
||||||
|
# Filter sessions by source
|
||||||
|
hermes sessions list --source telegram
|
||||||
|
hermes sessions export telegram_sessions.jsonl --source telegram
|
||||||
|
|
||||||
|
# Prune old sessions (with confirmation prompt)
|
||||||
|
hermes sessions prune --older-than 30
|
||||||
|
# Without confirmation (for no_agent scripts):
|
||||||
|
hermes sessions prune --older-than 30 --yes
|
||||||
|
```
|
||||||
|
**Verified:** All commands tested. Export uses positional output arg, not `--output`. List does not support `--json`. Prune uses `--yes` not `--confirm`. [VERIFIED: Hermes CLI v0.16.0]
|
||||||
|
|
||||||
|
### Command 7: Post-archive verification (dry-run procedure)
|
||||||
|
```bash
|
||||||
|
# Step 1: Manual export (dry-run — inspect before enabling prune)
|
||||||
|
hermes sessions export ~/.hermes/archive/sessions/sessions-$(date +%Y%m%d)-dryrun.jsonl
|
||||||
|
|
||||||
|
# Step 2: Review the exported JSONL
|
||||||
|
wc -l ~/.hermes/archive/sessions/sessions-*-dryrun.jsonl
|
||||||
|
# Check for sensitive data, verify format
|
||||||
|
|
||||||
|
# Step 3: Check which sessions would be pruned (no --dry-run flag on prune)
|
||||||
|
# Use export + filter to preview:
|
||||||
|
hermes sessions export - | python3 -c "
|
||||||
|
import sys, json, time
|
||||||
|
now = time.time()
|
||||||
|
for line in sys.stdin:
|
||||||
|
if line.strip():
|
||||||
|
s = json.loads(line)
|
||||||
|
age_days = (now - s['last_active']) / 86400
|
||||||
|
if age_days > 30:
|
||||||
|
print(f'WOULD PRUNE: {s[\"id\"]} — last active {age_days:.0f}d ago')
|
||||||
|
"
|
||||||
|
|
||||||
|
# Step 4: Only after review, enable prune in the cron script
|
||||||
|
```
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| Manual session management | Automated cron-driven lifecycle | Phase 8 | Sessions auto-archived after 30d inactivity, no manual cleanup needed |
|
||||||
|
| No reporting | Daily Telegram summaries with Jira progress | Phase 8 | Regular operational visibility without user having to ask |
|
||||||
|
| Jira tickets created but not tracked | Daily progress comments on linked tickets | Phase 8 | Jira tickets reflect real session activity automatically |
|
||||||
|
|
||||||
|
**Deprecated/outdated:**
|
||||||
|
- No deprecated approaches — this phase introduces new capabilities not previously available in the system.
|
||||||
|
|
||||||
|
## Assumptions Log
|
||||||
|
|
||||||
|
| # | Claim | Section | Risk if Wrong |
|
||||||
|
|---|-------|---------|---------------|
|
||||||
|
| A1 | `hindsight_recall` tool is available in Hermes cron agent context | Architecture Patterns — Jira-session mapping | Low — agent can fall back to `session_search` or export + grep for ticket key patterns |
|
||||||
|
| A2 | Phase 7 session skill successfully saved session summaries to hindsight with Jira ticket keys | Architecture Patterns | Medium — if session skill was not active for older sessions, those sessions have no hindsight record; daily report would miss their Jira links |
|
||||||
|
| A3 | 1-to-many session→Jira mapping (one session can update multiple Jira tickets) | Requirements — CRON-03 | Low — depends on how Phase 7 session skill records Jira tickets; the agent can update multiple tickets if it finds multiple keys |
|
||||||
|
| A4 | All sessions in DB have `ended_at` set (not just active sessions) | Patterns — archive script | Low — prune behavior depends on this; if active sessions have `ended_at: null`, prune may not delete them (which is correct) |
|
||||||
|
|
||||||
|
## Open Questions (RESOLVED)
|
||||||
|
|
||||||
|
1. **Does `hermes sessions prune --older-than 30` only delete sessions where `ended_at` is not null?** — RESOLVED: Plan 08-01 archive script uses `hermes sessions export` first (captures ALL sessions to JSONL), then runs `prune --older-than 30 --yes`. If prune only affects ended sessions, active-but-idle sessions are safely preserved. The export captures everything regardless, so no data loss either way.
|
||||||
|
|
||||||
|
2. **Does `session_search` tool work in cron agent context?** — RESOLVED: Plan 08-01 daily report cron prompt instructs agent to use `hermes sessions export -` (JSONL to stdout) as the primary data source, with `hindsight_recall` for Jira-session mapping. No dependency on `session_search` availability in cron context.
|
||||||
|
|
||||||
|
3. **How does the agent determine "active" vs "stale"?** — RESOLVED: The daily report prompt tells the agent to use `hermes sessions export -` and parse the JSONL output (via python3 in Docker) to filter sessions by `last_active` timestamp. Stale = `last_active` > 30 days. Active = `last_active` < 24h.
|
||||||
|
|
||||||
|
## Environment Availability
|
||||||
|
|
||||||
|
| Dependency | Required By | Available | Version | Fallback |
|
||||||
|
|------------|------------|-----------|---------|----------|
|
||||||
|
| `hermes` CLI | All cron jobs | ✓ | v0.16.0 | — |
|
||||||
|
| Hermes gateway | Cron scheduler | ✓ | Running (PID 3178) | — |
|
||||||
|
| `ngn-jira` script | Daily report Jira comments | ✓ | ~/.hermes/scripts/ngn-jira | — |
|
||||||
|
| `bash` | Archive script | ✓ | system | — |
|
||||||
|
| `date` command | Archive filename timestamp | ✓ | system | — |
|
||||||
|
| `mkdir` | Archive directory creation | ✓ | system | — |
|
||||||
|
| `jq` (optional) | JSONL filtering | ✗ | — | Use `python3 -c` or grep instead |
|
||||||
|
| `python3` | JSONL parsing (fallback) | ✓ | system | — |
|
||||||
|
|
||||||
|
**Missing dependencies with no fallback:** None — all required tools are available.
|
||||||
|
|
||||||
|
**Missing dependencies with fallback:** `jq` — the archive script and cron agents can use `python3 -c` for JSONL parsing instead.
|
||||||
|
|
||||||
|
## Security Domain
|
||||||
|
|
||||||
|
> `security_enforcement` not explicitly disabled; include security considerations.
|
||||||
|
|
||||||
|
### Applicable ASVS Categories
|
||||||
|
|
||||||
|
| ASVS Category | Applies | Standard Control |
|
||||||
|
|---------------|---------|-----------------|
|
||||||
|
| V2 Authentication | no | — |
|
||||||
|
| V3 Session Management | no | — |
|
||||||
|
| V4 Access Control | no | — |
|
||||||
|
| V5 Input Validation | no | — |
|
||||||
|
| V6 Cryptography | no | — |
|
||||||
|
|
||||||
|
### Known Threat Patterns for Hermes Cron
|
||||||
|
|
||||||
|
| Pattern | STRIDE | Standard Mitigation |
|
||||||
|
|---------|--------|---------------------|
|
||||||
|
| Cron job prompt injection | Tampering | Prompt is authored by the implementer and stored in Hermes config; not user-controllable |
|
||||||
|
| Archive file writes to home dir | Tampering | Script uses `$HOME/.hermes/archive/sessions/` — within protected Hermes data directory |
|
||||||
|
| Jira API token exposure via cron output | Information Disclosure | Cron output delivered to Telegram DM only (not logs). `wrap_response: true` in config may wrap output, reducing inline token leak risk |
|
||||||
|
| Unauthorized session deletion | Tampering | `approvals.cron_mode: deny` — destructive operations require manual approval in gateway; no_agent script's `prune --yes` should be reviewed for safety |
|
||||||
|
|
||||||
|
### Specific Security Notes
|
||||||
|
|
||||||
|
- **Cron output delivery:** `--deliver telegram` sends to TELEGRAM_HOME_CHANNEL (user's DM). Output is not stored in logs (cron output directory is empty by default).
|
||||||
|
- **Archive data sensitivity:** JSONL export includes full session transcripts — may contain AWS account IDs, project names, Jira ticket details. Archive files should have the same protection as the Hermes data directory (`~/.hermes/`).
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- **Hermes CLI v0.16.0 (local)** — All cron and session commands tested directly via terminal
|
||||||
|
- `hermes cron create --help` — full syntax with all flags
|
||||||
|
- `hermes sessions export --help` — confirmed no `--older-than` flag
|
||||||
|
- `hermes sessions prune --help` — confirmed `--older-than` and `--yes` flags
|
||||||
|
- `hermes sessions list --help` — confirmed table output only, no `--json`
|
||||||
|
- `hermes cron edit --help` — confirmed `--add-skill`, `--remove-skill`, `--agent`, `--no-agent` flags
|
||||||
|
- **Session JSONL export** — Full session schema inspected: `id`, `source`, `title`, `last_active`, `archived`, `message_count`, `messages`, `model`, token/cost data
|
||||||
|
- **System timezone** — Confirmed UTC+8 (SGT): `date +%Z` → `+08`
|
||||||
|
- **Cron job creation test** — Created test job with `--deliver telegram`, confirmed "Next run: 2026-06-16T09:00:00+08:00"
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- **CONTEXT.md** — Locked decisions from user discussion (D-01 through D-19, discretion areas)
|
||||||
|
- **Phase 7 session skill** (`~/.hermes/skills/ngn-agent/session/SKILL.md`) — Jira ticket creation pattern, hindsight summary format
|
||||||
|
- **Phase 4 jira-query skill** (`~/.hermes/skills/ngn-agent/jira/SKILL.md`) — Jira API call patterns
|
||||||
|
- **`~/.hermes/config.yaml`** — `TELEGRAM_HOME_CHANNEL`, `cron:` section, `timezone:''`
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence) — None used
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH — All tools verified directly against Hermes CLI
|
||||||
|
- Architecture: HIGH — Patterns confirmed by user decisions and system capabilities
|
||||||
|
- Pitfalls: HIGH — Each pitfall verified against actual CLI behavior vs CONTEXT.md assumptions
|
||||||
|
|
||||||
|
**Research date:** 2026-06-15
|
||||||
|
**Valid until:** 2026-07-15 (Hermes is actively developed; CLI flags may change in future releases)
|
||||||
224
.planning/phases/09-tooling-portable-setup/09-01-PLAN.md
Normal file
224
.planning/phases/09-tooling-portable-setup/09-01-PLAN.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
---
|
||||||
|
phase: 09-tooling-portable-setup
|
||||||
|
plan: 01
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- ngn-agent/docker/Dockerfile
|
||||||
|
- ngn-agent/docker/build.sh
|
||||||
|
autonomous: true
|
||||||
|
requirements: [TOOL-01]
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "Custom Docker image can be built with a single command (docker/build.sh)"
|
||||||
|
- "Built image includes aws-cli v2, terraform, helm, kubectl, and datadog CLI (pup)"
|
||||||
|
- "Image is tagged ngn-agent:latest (local only, no registry push)"
|
||||||
|
- "Each tool version is pinned for reproducibility"
|
||||||
|
- "Build uses Dockerfile from the project repo (ngn-agent/docker/Dockerfile)"
|
||||||
|
artifacts:
|
||||||
|
- path: "ngn-agent/docker/Dockerfile"
|
||||||
|
provides: "Custom Hermes-compatible Docker image definition with 5 platform tools"
|
||||||
|
min_lines: 80
|
||||||
|
- path: "ngn-agent/docker/build.sh"
|
||||||
|
provides: "Single-command image build entry point"
|
||||||
|
min_lines: 15
|
||||||
|
key_links:
|
||||||
|
- from: "ngn-agent/docker/build.sh"
|
||||||
|
to: "ngn-agent/docker/Dockerfile"
|
||||||
|
via: "docker build -f"
|
||||||
|
pattern: "docker build.*-f.*Dockerfile"
|
||||||
|
- from: "Dockerfile"
|
||||||
|
to: "aws-cli v2"
|
||||||
|
via: "curl → unzip → aws/install"
|
||||||
|
pattern: "awscli\\.amazonaws\\.com/awscli-exe"
|
||||||
|
- from: "Dockerfile"
|
||||||
|
to: "terraform"
|
||||||
|
via: "HashiCorp apt repo"
|
||||||
|
pattern: "apt\\.releases\\.hashicorp\\.com"
|
||||||
|
- from: "Dockerfile"
|
||||||
|
to: "helm"
|
||||||
|
via: "Buildkite apt repo"
|
||||||
|
pattern: "packages\\.buildkite\\.com/helm"
|
||||||
|
- from: "Dockerfile"
|
||||||
|
to: "kubectl"
|
||||||
|
via: "Google Kubernetes apt repo"
|
||||||
|
pattern: "pkgs\\.k8s\\.io"
|
||||||
|
- from: "Dockerfile"
|
||||||
|
to: "pup (Datadog CLI)"
|
||||||
|
via: "GitHub releases binary download"
|
||||||
|
pattern: "github\\.com/DataDog/pup/releases"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**Custom Hermes Docker Image with Platform Engineering Tools**
|
||||||
|
|
||||||
|
Create a custom Docker image extending `nikolaik/python-nodejs:python3.11-nodejs20` with five platform engineering CLI tools pre-installed: AWS CLI v2, Terraform, Helm, kubectl, and Datadog CLI (pup). The image is buildable with a single `docker/build.sh` command and tagged `ngn-agent:latest` for local use.
|
||||||
|
|
||||||
|
**Purpose:** Eliminate per-session tool installation overhead — tools are baked into the Docker image so every Hermes session has immediate access to aws-cli, terraform, helm, kubectl, and pup without runtime installs.
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
- `ngn-agent/docker/Dockerfile` — Version-pinned tool installations on top of the base Hermes image
|
||||||
|
- `ngn-agent/docker/build.sh` — Single-command build entry point
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/phases/09-tooling-portable-setup/09-CONTEXT.md
|
||||||
|
@.planning/phases/09-tooling-portable-setup/09-RESEARCH.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Create Dockerfile with version-pinned tool installations</name>
|
||||||
|
<files>ngn-agent/docker/Dockerfile</files>
|
||||||
|
<action>
|
||||||
|
Create `ngn-agent/docker/Dockerfile` that:
|
||||||
|
|
||||||
|
1. **FROM** extends `nikolaik/python-nodejs:python3.11-nodejs20` (per D-01). Add a comment above FROM noting: "D-01: Base image tag python3.11-nodejs20 — verify availability at build time; if manifest not found, update to python3.11-nodejs22-bookworm (Node.js 20 EOL April 2026)."
|
||||||
|
|
||||||
|
2. **LABEL** with `description="ngn-agent: Custom Hermes Docker image with platform engineering tools"` and `maintainer="ngn-agent"`.
|
||||||
|
|
||||||
|
3. **ARGs for version pinning** (per D-02):
|
||||||
|
- `TERRAFORM_VERSION=1.15.6`
|
||||||
|
- `HELM_VERSION=4.2.1`
|
||||||
|
- `KUBECTL_VERSION=1.36.1`
|
||||||
|
- `PUPP_VERSION=1.1.0`
|
||||||
|
(AWS CLI v2 is the latest stable — per D-03, install without version pinning but pin to latest known at time of build.)
|
||||||
|
|
||||||
|
4. **Install system dependencies** in a single RUN:
|
||||||
|
`curl`, `ca-certificates`, `unzip`, `gnupg`, `wget` — clean apt lists after install.
|
||||||
|
|
||||||
|
5. **Install AWS CLI v2** (per D-03):
|
||||||
|
Use the official curl → unzip → `./aws/install` method (no apt repo for v2). Install to `/usr/local/bin` and `/usr/local/aws-cli`. Clean up zip and extracted files. Reference D-03 in a comment.
|
||||||
|
|
||||||
|
6. **Install Terraform** (per D-03):
|
||||||
|
Add HashiCorp GPG key, add HashiCorp apt repo, `apt-get install -y terraform=${TERRAFORM_VERSION}`. Clean apt lists. Reference D-03.
|
||||||
|
|
||||||
|
7. **Install kubectl** (per D-03):
|
||||||
|
Add Google Kubernetes GPG key, add kubernetes apt repo for v1.36, `apt-get install -y kubectl`. Do NOT version-pin kubectl (it must match the target cluster version — per D-03: "latest stable matching cluster version"). Clean apt lists. Reference D-03.
|
||||||
|
|
||||||
|
8. **Install Helm** (per D-03):
|
||||||
|
Add Buildkite GPG key, add Helm apt repo, `apt-get install -y helm=${HELM_VERSION}`. Clean apt lists. Reference D-03.
|
||||||
|
|
||||||
|
9. **Install Datadog CLI (pup)** (per D-03):
|
||||||
|
Download the Linux x86_64 tarball from GitHub Releases, extract `pup` binary to `/usr/local/bin/`, clean up. Reference D-03.
|
||||||
|
|
||||||
|
10. **Verify step**: Single RUN with `echo "=== Tool versions ===" && aws --version && terraform --version && helm version --short && kubectl version --client --output=yaml 2>/dev/null | grep gitVersion && pup --version`. Use `|| true` on version checks that may exit non-zero so build doesn't fail on a single tool being unreachable.
|
||||||
|
|
||||||
|
11. **CMD** `["bash"]` — matching base image behavior.
|
||||||
|
|
||||||
|
**Build hygiene:**
|
||||||
|
- Do NOT `COPY .` or any full-directory copy — this prevents leaking secrets into image layers (T-09-02 mitigation).
|
||||||
|
- Order tool installs from most-frequently-changed to least-changed to optimize layer caching.
|
||||||
|
- Use `--no-install-recommends` on all apt-get install commands.
|
||||||
|
- Remove apt lists after each apt install to keep image small.
|
||||||
|
- All tool downloads use HTTPS URLs (T-09-01 mitigation).
|
||||||
|
- Add inline comments referencing each D-NN decision for traceability.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>test -f /Users/bapung/Razer/ngn-agent/docker/Dockerfile && wc -l /Users/bapung/Razer/ngn-agent/docker/Dockerfile</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- Dockerfile exists at ngn-agent/docker/Dockerfile
|
||||||
|
- All 5 tools (aws-cli, terraform, helm, kubectl, pup) are installed via RUN commands
|
||||||
|
- Tool versions are pinned via ARGs (except kubectl which matches cluster version)
|
||||||
|
- D-01 through D-03 are referenced in comments
|
||||||
|
- No `COPY .` or directory-wide copy present
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Create build.sh and verify image builds successfully</name>
|
||||||
|
<files>ngn-agent/docker/build.sh</files>
|
||||||
|
<action>
|
||||||
|
Create `ngn-agent/docker/build.sh` (per D-04):
|
||||||
|
|
||||||
|
1. Shebang: `#!/bin/bash` with `set -euo pipefail`.
|
||||||
|
|
||||||
|
2. Constants:
|
||||||
|
- `IMAGE_NAME="ngn-agent"`
|
||||||
|
- `IMAGE_TAG="latest"`
|
||||||
|
- `DOCKER_DIR` resolved from script location: `"$(cd "$(dirname "$0")" && pwd)"`
|
||||||
|
|
||||||
|
3. Print build banner: `echo "==> Building ${IMAGE_NAME}:${IMAGE_TAG}..."`
|
||||||
|
|
||||||
|
4. `docker build` command:
|
||||||
|
- `-t "${IMAGE_NAME}:${IMAGE_TAG}"`
|
||||||
|
- `-f "${DOCKER_DIR}/Dockerfile"`
|
||||||
|
- Build context: `"${DOCKER_DIR}"` (not the repo root — prevents leaking files)
|
||||||
|
- Per D-05: tag is `ngn-agent:latest` (local only, no registry push)
|
||||||
|
|
||||||
|
5. Print completion banner: `echo "==> Build complete: ${IMAGE_NAME}:${IMAGE_TAG}"` followed by `docker images "${IMAGE_NAME}:${IMAGE_TAG}"`.
|
||||||
|
|
||||||
|
6. Make the script executable: `chmod +x /Users/bapung/Razer/ngn-agent/docker/build.sh`.
|
||||||
|
|
||||||
|
**Verify the build** by running `docker/build.sh` (or `bash docker/build.sh` if script path issues). If the base image tag `python3.11-nodejs20` fails with a manifest error, update the Dockerfile FROM line to `nikolaik/python-nodejs:python3.11-nodejs22-bookworm` as recommended by research, add a comment noting the change, and retry. After successful build:
|
||||||
|
|
||||||
|
- Run `docker run --rm ngn-agent:latest aws --version` — confirms aws-cli is installed and executable
|
||||||
|
- Run `docker run --rm ngn-agent:latest terraform --version` — confirms terraform works
|
||||||
|
- Run `docker run --rm ngn-agent:latest helm version --short` — confirms helm works
|
||||||
|
- Run `docker run --rm ngn-agent:latest kubectl version --client` — confirms kubectl works
|
||||||
|
- Run `docker run --rm ngn-agent:latest pup --version` — confirms pup works
|
||||||
|
|
||||||
|
If any tool fails in the container, fix the Dockerfile and rebuild. The image must have all 5 tools working.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>docker run --rm ngn-agent:latest sh -c 'aws --version && terraform --version && helm version --short && kubectl version --client && pup --version'</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- build.sh exists and is executable
|
||||||
|
- `ngn-agent:latest` image builds successfully
|
||||||
|
- All 5 tools execute correctly inside the container
|
||||||
|
- Build script references D-04 and D-05 decisions
|
||||||
|
- If base image tag was updated to python3.11-nodejs22-bookworm due to deprecation, the FROM line is updated and a comment explains the change
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Docker build host → Docker daemon | Docker context is read by build daemon |
|
||||||
|
| Docker build → external tool repos | Tool downloads cross HTTPS boundaries |
|
||||||
|
| Docker image layers → local storage | Built image stored on host filesystem |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-09-01 | Tampering | Tool download MITM (curl/wget from S3, GitHub, apt repos) | mitigate | All downloads use HTTPS. apt repos use GPG key verification to sign packages. |
|
||||||
|
| T-09-02 | Information Disclosure | Build context leak (COPY . could send .env into Docker daemon) | mitigate | Build context is `docker/` directory only — not repo root. Dockerfile does not COPY . anywhere. |
|
||||||
|
| T-09-03 | Tampering | Base image supply chain (nikolaik/python-nodejs) | accept | Widely used community image; no custom base image available. |
|
||||||
|
| T-09-04 | Denial of Service | Base image tag python3.11-nodejs20 may not exist | mitigate | Dockerfile comment flags potential deprecation. If build fails with manifest error, update to python3.11-nodejs22-bookworm. |
|
||||||
|
|
||||||
|
## Package Legitimacy Gate
|
||||||
|
|
||||||
|
| Package | Registry | Verdict | Disposition |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| (none) | — | — | No npm/pip/cargo packages installed. All tools via apt or curl binary downloads. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- Automated: `docker run --rm ngn-agent:latest sh -c 'aws --version && terraform --version && helm version --short && kubectl version --client && pup --version'` passes with all 5 tools
|
||||||
|
- Manual: User can inspect `docker/images` output to confirm tag and creation date
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
1. The image `ngn-agent:latest` exists in local Docker daemon
|
||||||
|
2. Running `docker run --rm ngn-agent:latest <tool> --version` succeeds for all 5 tools
|
||||||
|
3. `docker/build.sh` is the single entry point for rebuilding
|
||||||
|
4. Both files are committed to the ngn-agent git repo
|
||||||
|
5. Tool versions are pinned (ARGS in Dockerfile) for future reproducibility
|
||||||
|
</success_criteria>
|
||||||
|
</objective>
|
||||||
162
.planning/phases/09-tooling-portable-setup/09-01-SUMMARY.md
Normal file
162
.planning/phases/09-tooling-portable-setup/09-01-SUMMARY.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
---
|
||||||
|
phase: 09-tooling-portable-setup
|
||||||
|
plan: 01
|
||||||
|
subsystem: infra
|
||||||
|
tags: docker, aws-cli, terraform, helm, kubectl, datadog-cli, arm64
|
||||||
|
|
||||||
|
# Dependency graph
|
||||||
|
requires:
|
||||||
|
- phase: 09-tooling-portable-setup
|
||||||
|
provides: Research on tool versions, architecture decisions, installation methods
|
||||||
|
provides:
|
||||||
|
- Custom Hermes Docker image (ngn-agent:latest) with 5 platform engineering CLI tools
|
||||||
|
- Version-pinned, reproducible Dockerfile with architecture detection (x86_64 + arm64)
|
||||||
|
- Single-command build entry point (docker/build.sh)
|
||||||
|
affects: [09-tooling-portable-setup (plan 02 - setup script)]
|
||||||
|
|
||||||
|
# Tech tracking
|
||||||
|
tech-stack:
|
||||||
|
added:
|
||||||
|
- Dockerfile multi-tool build pattern
|
||||||
|
- Architecture-detection case/esac for binary downloads
|
||||||
|
patterns:
|
||||||
|
- Version pinning via ARGs for reproducibility
|
||||||
|
- Multi-architecture support via uname -m detection
|
||||||
|
- GPG-verified apt repos for tool installation
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- ngn-agent/docker/Dockerfile
|
||||||
|
- ngn-agent/docker/build.sh
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Helm version 4.2.1 not in Buildkite apt repo; pinned to 4.2.0 instead"
|
||||||
|
- "Terraform apt version format requires -1 suffix (terraform=1.15.6-1)"
|
||||||
|
- "Added architecture detection for AWS CLI and pup (x86_64 + aarch64) for native ARM64 support"
|
||||||
|
- "Used /etc/os-release instead of lsb_release (not available in base image)"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Multi-tool Dockerfile: version-pinned ARGs, GPG-verified apt repos, architecture detection for binary downloads"
|
||||||
|
|
||||||
|
requirements-completed: [TOOL-01]
|
||||||
|
|
||||||
|
# Metrics
|
||||||
|
duration: 6 min
|
||||||
|
completed: 2026-06-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 9 Plan 1: Custom Hermes Docker Image with Platform Engineering Tools
|
||||||
|
|
||||||
|
**Version-pinned Docker image (ngn-agent:latest) with aws-cli, terraform, helm, kubectl, and datadog CLI (pup), buildable via a single docker/build.sh command — with native ARM64 support for Apple Silicon.**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 6 min
|
||||||
|
- **Started:** 2026-06-15T15:18:47Z
|
||||||
|
- **Completed:** 2026-06-15T15:24:38Z
|
||||||
|
- **Tasks:** 2
|
||||||
|
- **Files modified:** 2
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Created `ngn-agent/docker/Dockerfile` — version-pinned installations of 5 platform engineering tools on top of `nikolaik/python-nodejs:python3.11-nodejs20`
|
||||||
|
- Created `ngn-agent/docker/build.sh` — single-command build entry point with `set -euo pipefail`
|
||||||
|
- Built and verified `ngn-agent:latest` image with all 5 tools working natively on ARM64 (Apple Silicon)
|
||||||
|
- Added architecture detection (`uname -m`) for AWS CLI and pup binary downloads supporting both x86_64 and aarch64
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Create Dockerfile with version-pinned tool installations** - `78fd400` (feat)
|
||||||
|
2. **Task 2: Create build.sh and verify image builds successfully** - `2797a64` (feat, includes deviation fixes)
|
||||||
|
3. **Task 2 follow-up: Add D-04/D-05 references to build.sh** - `cc1da75` (docs)
|
||||||
|
|
||||||
|
**Plan metadata:** `(committed as part of Task 2 commits)`
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `ngn-agent/docker/Dockerfile` (112 lines) — Multi-architecture Dockerfile with 5 platform engineering tools, version-pinned via ARGs, GPG-verified apt repos, architecture detection for binary downloads
|
||||||
|
- `ngn-agent/docker/build.sh` (26 lines) — Single-command build entry point, resolves script location for correct build context (T-09-02 mitigation)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **Helm version 4.2.0** used instead of planned 4.2.1 — 4.2.1 doesn't exist in Buildkite apt repo
|
||||||
|
- **Architecture detection** added for AWS CLI and pup — base image runs on ARM64 natively (Apple Silicon), x86_64 binaries would need QEMU emulation
|
||||||
|
- **Terraform version string** uses `-1` suffix for apt compatibility (`terraform=1.15.6-1`)
|
||||||
|
- **`/etc/os-release`** used for codename detection instead of `lsb_release` (not shipped in base image)
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
### Auto-fixed Issues
|
||||||
|
|
||||||
|
**1. [Rule 3 - Blocking] `lsb_release` not found in base image**
|
||||||
|
- **Found during:** Task 1 (Dockerfile creation, first build attempt)
|
||||||
|
- **Issue:** `lsb_release` is not installed in the base image, causing the terraform apt repo codename resolution to fail with "Malformed entry"
|
||||||
|
- **Fix:** Replaced `$(lsb_release -cs)` with `. /etc/os-release && echo ${VERSION_CODENAME}` for Debian codename detection
|
||||||
|
- **Files modified:** `ngn-agent/docker/Dockerfile`
|
||||||
|
- **Verification:** Build succeeded, codename resolved to `trixie`, terraform installed correctly
|
||||||
|
- **Committed in:** `2797a64` (Task 2 commit)
|
||||||
|
|
||||||
|
**2. [Rule 1 - Bug] Hardcoded x86_64 binary downloads fail on ARM64 (Apple Silicon)**
|
||||||
|
- **Found during:** Task 2 (build verification — tools hung via QEMU)
|
||||||
|
- **Issue:** AWS CLI and pup binaries were hardcoded to x86_64 URLs. On Apple Silicon, the base image runs natively on ARM64, and x86_64 binaries triggered QEMU emulation that hung without proper `/lib64/ld-linux-x86-64.so.2`
|
||||||
|
- **Fix:** Added architecture detection via `uname -m` with case/esac for both AWS CLI (`awscli-exe-linux-{arch}.zip`) and pup (`pup_${version}_Linux_{arch}.tar.gz`) downloads
|
||||||
|
- **Files modified:** `ngn-agent/docker/Dockerfile`
|
||||||
|
- **Verification:** All 5 tools now run natively on ARM64 (aarch64) without QEMU warnings. `aws --version` reports `exe/aarch64.debian.13`, terraform reports `linux_arm64`
|
||||||
|
- **Committed in:** `2797a64` (Task 2 commit)
|
||||||
|
|
||||||
|
**3. [Rule 1 - Bug] Terraform version string missing -1 suffix**
|
||||||
|
- **Found during:** Task 2 (build attempt — apt version not found)
|
||||||
|
- **Issue:** `apt-get install terraform=1.15.6` failed with "Version '1.15.6' not found" because HashiCorp apt repo uses version format `1.15.6-1`
|
||||||
|
- **Fix:** Changed install line to `terraform=${TERRAFORM_VERSION}-1`
|
||||||
|
- **Files modified:** `ngn-agent/docker/Dockerfile`
|
||||||
|
- **Verification:** Terraform 1.15.6-1 installed and runs successfully
|
||||||
|
- **Committed in:** `2797a64` (Task 2 commit)
|
||||||
|
|
||||||
|
**4. [Rule 1 - Bug] Helm version 4.2.1 not found in Buildkite apt repo**
|
||||||
|
- **Found during:** Task 2 (build attempt — apt version not found)
|
||||||
|
- **Issue:** Helm 4.2.1 doesn't exist in the Buildkite apt repo; latest available is 4.2.0-1
|
||||||
|
- **Fix:** Changed `HELM_VERSION` ARG from `4.2.1` to `4.2.0`
|
||||||
|
- **Files modified:** `ngn-agent/docker/Dockerfile`
|
||||||
|
- **Verification:** Helm 4.2.0-1 installed successfully, `helm version --short` reports `v4.2.0+g0646808`
|
||||||
|
- **Committed in:** `2797a64` (Task 2 commit)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Total deviations:** 4 auto-fixed (2 bugs, 1 blocking, 1 version correction)
|
||||||
|
**Impact on plan:** All fixes essential for build to succeed and tools to work correctly on the target architecture. No scope creep.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
- Base image `nikolaik/python-nodejs:python3.11-nodejs20` is still available on Docker Hub (as of 2026-06-15) — the planned deprecation did not occur, so the original tag was used
|
||||||
|
- The base image is multi-architecture (arm64 + amd64); on Apple Silicon, Docker selects the arm64 variant automatically. Binary downloads for tools without native ARM64 builds were fixed with architecture detection
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
None - no external service configuration required. Run `docker/build.sh` to rebuild the image.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Docker image `ngn-agent:latest` is built and verified with all 5 tools
|
||||||
|
- Ready for Plan 2 (portable setup script) which will reference this image in `~/.hermes/config.yaml`
|
||||||
|
- The base image tag `nikolaik/python-nodejs:python3.11-nodejs20` should be monitored — if it gets deprecated, update to `python3.11-nodejs22-bookworm` as documented in Dockerfile comment
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
All files and commits verified:
|
||||||
|
- ✅ `docker/Dockerfile` exists (112 lines)
|
||||||
|
- ✅ `docker/build.sh` exists (26 lines, executable)
|
||||||
|
- ✅ `09-01-SUMMARY.md` exists
|
||||||
|
- ✅ Commit `78fd400` — feat: Dockerfile creation
|
||||||
|
- ✅ Commit `2797a64` — feat: build.sh + verification + fixes
|
||||||
|
- ✅ Commit `cc1da75` — docs: D-04/D-05 references
|
||||||
|
- ✅ Commit `717bb6f` — docs: plan SUMMARY
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 09-tooling-portable-setup*
|
||||||
|
*Completed: 2026-06-15*
|
||||||
478
.planning/phases/09-tooling-portable-setup/09-02-PLAN.md
Normal file
478
.planning/phases/09-tooling-portable-setup/09-02-PLAN.md
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
---
|
||||||
|
phase: 09-tooling-portable-setup
|
||||||
|
plan: 02
|
||||||
|
type: execute
|
||||||
|
wave: 1
|
||||||
|
depends_on: []
|
||||||
|
files_modified:
|
||||||
|
- ngn-agent/setup-ngn-agent.sh
|
||||||
|
autonomous: true
|
||||||
|
requirements: [SETUP-01]
|
||||||
|
|
||||||
|
user_setup:
|
||||||
|
- service: hermes
|
||||||
|
why: "Setup script assumes Hermes v0.16+ is installed"
|
||||||
|
env_vars:
|
||||||
|
- name: JIRA_API_TOKEN
|
||||||
|
source: "https://id.atlassian.com/manage/api-tokens"
|
||||||
|
- name: JIRA_EMAIL
|
||||||
|
source: "Your Atlassian account email"
|
||||||
|
- name: TELEGRAM_BOT_TOKEN
|
||||||
|
source: "https://t.me/BotFather — create a bot and copy the token"
|
||||||
|
- name: OPENROUTER_API_KEY
|
||||||
|
source: "https://openrouter.ai/keys (or reuse existing)"
|
||||||
|
|
||||||
|
must_haves:
|
||||||
|
truths:
|
||||||
|
- "User can run setup-ngn-agent.sh on a fresh macOS machine with Hermes installed"
|
||||||
|
- "Script validates prerequisites (Hermes on PATH, Docker running, SSH keys exist) before making changes"
|
||||||
|
- "Script prompts for all 4 secrets (JIRA_API_TOKEN, JIRA_EMAIL, TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY) with masked input"
|
||||||
|
- "Script accepts configurable paths via args or prompts (SSH keys, repo paths, timezone)"
|
||||||
|
- "After running, ~/.hermes/config.yaml has correct docker_volumes, shell_init_files, docker_forward_env, and docker_image"
|
||||||
|
- "After running, ~/.hermes/.env has all secrets and DEFAULT_REPOS"
|
||||||
|
- "After running, ~/.hermes/hindsight/config.json has Hindsight local_embedded config"
|
||||||
|
- "After running, ~/.hermes/scripts/ has session-init.sh and archive-stale-sessions.sh"
|
||||||
|
- "After running, ~/.hermes/skills/ngn-agent/ has all 5 skills with reference files"
|
||||||
|
- "After running, 3 cron jobs (ngn-daily-report, ngn-weekly-stale-summary, ngn-weekly-archive) are registered"
|
||||||
|
- "After running, docker_image in config.yaml points to ngn-agent:latest"
|
||||||
|
- "Script backups existing config.yaml before modifying"
|
||||||
|
- "Script offers to restart Hermes gateway at end"
|
||||||
|
artifacts:
|
||||||
|
- path: "ngn-agent/setup-ngn-agent.sh"
|
||||||
|
provides: "Portable one-shot macOS setup script for ngn-agent configuration"
|
||||||
|
min_lines: 300
|
||||||
|
key_links:
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "~/.hermes/config.yaml"
|
||||||
|
via: "hermes config set + Python/sed for docker_volumes"
|
||||||
|
pattern: "hermes config set|config\\.yaml"
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "~/.hermes/.env"
|
||||||
|
via: "cat/write secrets to .env"
|
||||||
|
pattern: "(JIRA_API_TOKEN|TELEGRAM_BOT_TOKEN)"
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "~/.hermes/hindsight/config.json"
|
||||||
|
via: "write JSON config"
|
||||||
|
pattern: "hindsight/config\\.json"
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "~/.hermes/scripts/"
|
||||||
|
via: "heredoc or cat file contents to scripts/"
|
||||||
|
pattern: "(session-init|archive-stale)"
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "~/.hermes/skills/ngn-agent/"
|
||||||
|
via: "mkdir -p SKILL.md per skill"
|
||||||
|
pattern: "skills/ngn-agent"
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "Hermes cron DB"
|
||||||
|
via: "hermes cron create"
|
||||||
|
pattern: "hermes cron create"
|
||||||
|
- from: "setup-ngn-agent.sh"
|
||||||
|
to: "Docker image"
|
||||||
|
via: "hermes config set terminal.docker_image"
|
||||||
|
pattern: "docker_image ngn-agent"
|
||||||
|
---
|
||||||
|
|
||||||
|
<objective>
|
||||||
|
**Portable ngn-agent Setup Script**
|
||||||
|
|
||||||
|
Create a single portable bash script (`ngn-agent/setup-ngn-agent.sh`) that can recreate all ngn-agent configuration on a fresh macOS machine with Hermes v0.16+ installed. The script handles prerequisite validation, interactive secret prompts, config file generation (config.yaml, .env, hindsight/config.json), skill/script file creation, cron job registration, and Docker image reference updates — all in one invocation.
|
||||||
|
|
||||||
|
**Purpose:** Enable a new machine to be fully configured with ngn-agent's workspace setup (SSH mounts, repo mounts, skills, cron reporting, hindsight memory) by running a single script with parameterized paths and interactive secret entry.
|
||||||
|
|
||||||
|
**Output:**
|
||||||
|
- `ngn-agent/setup-ngn-agent.sh` — Standalone portable setup script (self-contained, no external file dependencies)
|
||||||
|
</objective>
|
||||||
|
|
||||||
|
<execution_context>
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/workflows/execute-plan.md
|
||||||
|
@/Users/bapung/.config/opencode/gsd-core/templates/summary.md
|
||||||
|
</execution_context>
|
||||||
|
|
||||||
|
<context>
|
||||||
|
@.planning/ROADMAP.md
|
||||||
|
@.planning/phases/09-tooling-portable-setup/09-CONTEXT.md
|
||||||
|
@.planning/phases/09-tooling-portable-setup/09-RESEARCH.md
|
||||||
|
@/Users/bapung/.hermes/config.yaml
|
||||||
|
@/Users/bapung/.hermes/.env
|
||||||
|
@/Users/bapung/.hermes/hindsight/config.json
|
||||||
|
@/Users/bapung/.hermes/scripts/session-init.sh
|
||||||
|
@/Users/bapung/.hermes/scripts/archive-stale-sessions.sh
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/jira/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/aws-diagnostics/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/confluence/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/bitbucket/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/session/SKILL.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/aws-diagnostics/references/multiregional-patterns.md
|
||||||
|
@/Users/bapung/.hermes/skills/ngn-agent/session/references/operational-monitoring.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-01-SUMMARY.md
|
||||||
|
@.planning/phases/08-cron-reporting/08-02-SUMMARY.md
|
||||||
|
</context>
|
||||||
|
|
||||||
|
<tasks>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 1: Create setup script skeleton — args, prereqs, and interactive prompts</name>
|
||||||
|
<files>ngn-agent/setup-ngn-agent.sh</files>
|
||||||
|
<action>
|
||||||
|
Create `ngn-agent/setup-ngn-agent.sh` starting with the script skeleton, argument parsing, prerequisite validation, and interactive secret prompts. This is the first part of the file (approximately first 120 lines).
|
||||||
|
|
||||||
|
**Script header:**
|
||||||
|
- Shebang: `#!/bin/bash` with `set -euo pipefail` (per D-06)
|
||||||
|
- Comment header block: `# setup-ngn-agent.sh — Portable ngn-agent configuration setup`
|
||||||
|
- Include: `# Phase 9, Plan 2 — recreates all configuration on a fresh macOS machine`
|
||||||
|
- Include: `# Assumes Hermes v0.16+ is installed (per D-07)`
|
||||||
|
- Include: `# Embedded file snapshots frozen at: $(date +%Y-%m-%d)` (per Common Pitfall 4)
|
||||||
|
|
||||||
|
**Argument parsing** (per D-09):
|
||||||
|
Use `getopts` for named arguments with defaults:
|
||||||
|
- `-s1 | --ssh-key-1` — SSH private key path 1 (default: `~/.ssh/id_ed25519razer`)
|
||||||
|
- `-s2 | --ssh-key-2` — SSH private key path 2 (default: `~/.ssh/id_rsa`)
|
||||||
|
- `-sc | --ssh-config` — SSH config path (default: `~/.ssh/config`)
|
||||||
|
- `-sh | --ssh-known-hosts` — SSH known_hosts path (default: `~/.ssh/known_hosts`)
|
||||||
|
- `-r1 | --repo-ops` — rai-ops repo path (default: `~/Razer/rai-ops`)
|
||||||
|
- `-r2 | --repo-deploy` — rai-deployment repo path (default: `~/Razer/rai-deployment`)
|
||||||
|
- `-r3 | --repo-devtools` — rai-devtools repo path (default: `~/Razer/rai-devtools`)
|
||||||
|
- `-t | --timezone` — Timezone (default: `Asia/Singapore`)
|
||||||
|
- `-d | --docker-image` — Docker image tag (default: `ngn-agent:latest`)
|
||||||
|
- `-y | --yes` — Non-interactive mode (skip prompts, use defaults for secrets if set via env)
|
||||||
|
- `-h | --help` — Show usage
|
||||||
|
|
||||||
|
Display usage info when `-h` is passed, showing all parameters with defaults.
|
||||||
|
|
||||||
|
**Prerequisite checks** (per CONTEXT.md "Specific Ideas" > "should validate prerequisites"):
|
||||||
|
1. Check Hermes CLI is installed: `command -v hermes >/dev/null 2>&1 || { echo "ERROR: Hermes not found — install v0.16+ first"; exit 1; }`
|
||||||
|
2. Check Docker is running: `docker info >/dev/null 2>&1 || { echo "ERROR: Docker not running"; exit 1; }`
|
||||||
|
3. Check SSH key files exist (the two key paths from args): test `-f` each, warn if missing but don't abort (user can skip keys).
|
||||||
|
4. Check repo paths exist: test `-d` each, warn if missing but don't abort.
|
||||||
|
5. Print a summary of paths being used.
|
||||||
|
|
||||||
|
**Interactive secret prompts** (per D-08):
|
||||||
|
Define a `prompt_secret` function:
|
||||||
|
- Args: var_name, prompt_text, is_optional (default: false)
|
||||||
|
- Use `read -s -p` for masked input (T-09-05 mitigation — no echo to terminal)
|
||||||
|
- If not optional, loop until non-empty value provided
|
||||||
|
- If the env var is already set (e.g., user exported it), skip the prompt
|
||||||
|
- Return the value via `echo`
|
||||||
|
|
||||||
|
Prompt for these secrets (D-08):
|
||||||
|
1. `JIRA_API_TOKEN` — required — "JIRA API Token (https://id.atlassian.com/manage/api-tokens): "
|
||||||
|
2. `JIRA_EMAIL` — required — "JIRA Email: "
|
||||||
|
3. `TELEGRAM_BOT_TOKEN` — required — "Telegram Bot Token (from @BotFather): "
|
||||||
|
4. `OPENROUTER_API_KEY` — optional (check if already set in env) — "OpenRouter API Key (leave blank to keep existing): "
|
||||||
|
5. `HINDSIGHT_LLM_API_KEY` — optional (defaults to same as OPENROUTER_API_KEY)
|
||||||
|
|
||||||
|
Store prompted values in variables for use in Task 2.
|
||||||
|
|
||||||
|
**Config directory creation:**
|
||||||
|
- `mkdir -p ~/.hermes/scripts`
|
||||||
|
- `mkdir -p ~/.hermes/hindsight`
|
||||||
|
- `mkdir -p ~/.hermes/skills/ngn-agent`
|
||||||
|
- `mkdir -p ~/.hermes/archive/sessions`
|
||||||
|
|
||||||
|
**Backup existing config** (per Anti-Pattern 4):
|
||||||
|
- If `~/.hermes/config.yaml` exists, copy to `~/.hermes/config.yaml.bak.$(date +%Y%m%d_%H%M%S)` before modifying (T-09-07 mitigation).
|
||||||
|
|
||||||
|
Make the script executable: `chmod +x /Users/bapung/Razer/ngn-agent/setup-ngn-agent.sh`.
|
||||||
|
|
||||||
|
**Embedded content strategy:**
|
||||||
|
- All skill files, script files, and reference files are embedded as heredocs (not base64 — bash heredocs with quoted delimiters preserve file content without encoding overhead for text files).
|
||||||
|
- Each embedded file is a bash function that can be called to write its content to the target path.
|
||||||
|
- The function names follow the pattern: `write_<name>` (e.g., `write_session_init`, `write_jira_skill`).
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Reference D-06 through D-09 in comments where applicable.
|
||||||
|
- Use `echo " → [message]"` for progress output.
|
||||||
|
- Color/formatting: optional but keep simple (no dependencies).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>test -f /Users/bapung/Razer/ngn-agent/setup-ngn-agent.sh && head -20 /Users/bapung/Razer/ngn-agent/setup-ngn-agent.sh | grep -q 'setup-ngn-agent'</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- setup-ngn-agent.sh exists and is executable
|
||||||
|
- Argument parsing with getopts handles all 9 parameters with defaults
|
||||||
|
- Usage displayed with `-h`
|
||||||
|
- Prerequisite checks validate Hermes CLI, Docker, SSH key files, repo paths
|
||||||
|
- Interactive `prompt_secret` function implemented for masked input
|
||||||
|
- All 4 secrets prompted (JIRA_API_TOKEN, JIRA_EMAIL, TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY)
|
||||||
|
- Existing config.yaml is backed up before modification
|
||||||
|
- Embedded file write functions are defined (content filled in Task 2 and Task 3)
|
||||||
|
- D-06 through D-09 referenced in comments
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 2: Implement config generation — config.yaml, .env, hindsight/config.json</name>
|
||||||
|
<files>ngn-agent/setup-ngn-agent.sh</files>
|
||||||
|
<action>
|
||||||
|
Append (or fill in) the config generation functions in `setup-ngn-agent.sh`. These come after the Task 1 skeleton.
|
||||||
|
|
||||||
|
**Function: `generate_config_yaml`**
|
||||||
|
Write a new `~/.hermes/config.yaml` using `hermes config set` for simple keys and Python for complex YAML structures (per Research Pattern 3 — `hermes config set` for scalars, Python for arrays).
|
||||||
|
|
||||||
|
Keys to set via `hermes config set`:
|
||||||
|
- `terminal.backend docker`
|
||||||
|
- `terminal.docker_image ${DOCKER_IMAGE}` (default `ngn-agent:latest` — per D-10 "Update Docker image reference in config.yaml")
|
||||||
|
- `terminal.cwd /workspace`
|
||||||
|
- `terminal.container_memory 5120`
|
||||||
|
- `terminal.container_disk 51200`
|
||||||
|
- `terminal.container_cpu 1`
|
||||||
|
- `terminal.lifetime_seconds 300`
|
||||||
|
- `memory.provider hindsight`
|
||||||
|
- `terminal.timezone ${TIMEZONE}`
|
||||||
|
- `telegram.reactions false`
|
||||||
|
- `terminal.docker_env.AWS_REGION us-east-1`
|
||||||
|
- `terminal.container_persistent true`
|
||||||
|
- `terminal.docker_mount_cwd_to_workspace true`
|
||||||
|
|
||||||
|
For complex structures (docker_volumes array, shell_init_files, docker_forward_env), use Python 3 with `yaml` module. Write a Python script inline that:
|
||||||
|
1. Loads existing config.yaml (created by hermes config set calls)
|
||||||
|
2. Sets `terminal.docker_volumes` to the list:
|
||||||
|
```
|
||||||
|
"${SSH_KEY_1}:/root/.ssh/id_ed25519razer:ro",
|
||||||
|
"${SSH_KEY_2}:/root/.ssh/id_rsa:ro",
|
||||||
|
"${SSH_CONFIG}:/root/.ssh/config:ro",
|
||||||
|
"${SSH_KNOWN_HOSTS}:/root/.ssh/known_hosts:ro",
|
||||||
|
"${HOME}/.aws/config:/root/.aws/config:ro",
|
||||||
|
"${HOME}/.aws/sso/cache:/root/.aws/sso/cache:rw",
|
||||||
|
"${REPO_OPS}:/workspace/rai-ops:rw",
|
||||||
|
"${REPO_DEPLOY}:/workspace/rai-deployment:rw",
|
||||||
|
"${REPO_DEVTOOLS}:/workspace/rai-devtools:rw",
|
||||||
|
"${HOME}/.hermes/scripts:/usr/local/bin:ro",
|
||||||
|
```
|
||||||
|
3. Sets `terminal.docker_forward_env` to `['JIRA_EMAIL', 'JIRA_API_TOKEN', 'DEFAULT_REPOS']`
|
||||||
|
4. Sets `terminal.shell_init_files` to `['/usr/local/bin/session-init.sh']`
|
||||||
|
5. Writes back to config.yaml
|
||||||
|
If `python3` + `yaml` module is not available, fall back to a `sed`-based approach using heredoc templates.
|
||||||
|
|
||||||
|
**Function: `generate_env_file`**
|
||||||
|
Write `~/.hermes/.env` with:
|
||||||
|
1. Comment header: `# ngn-agent Environment — generated by setup-ngn-agent.sh`
|
||||||
|
2. Required vars (D-08):
|
||||||
|
- `OPENROUTER_API_KEY=<value>` (if provided)
|
||||||
|
- `JIRA_API_TOKEN=<value>`
|
||||||
|
- `JIRA_EMAIL=<value>`
|
||||||
|
- `TELEGRAM_BOT_TOKEN=<value>`
|
||||||
|
- `HINDSIGHT_LLM_API_KEY=<value>` (defaults to OPENROUTER_API_KEY if not separately provided)
|
||||||
|
3. Config vars:
|
||||||
|
- `DEFAULT_REPOS=rai-ops,rai-deployment,rai-devtools`
|
||||||
|
- `TELEGRAM_ALLOWED_USERS=474440517`
|
||||||
|
- `TERMINAL_TIMEOUT=60`
|
||||||
|
- `TERMINAL_LIFETIME_SECONDS=300`
|
||||||
|
4. Set file permissions: `chmod 600 ~/.hermes/.env` (T-09-06 mitigation — prevent world-readable secrets)
|
||||||
|
Include comment separators between sections matching the original .env style.
|
||||||
|
|
||||||
|
**Function: `generate_hindsight_config`**
|
||||||
|
Write `~/.hermes/hindsight/config.json` with exact content from current config (D-10):
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mode": "local_embedded",
|
||||||
|
"llm_provider": "openrouter",
|
||||||
|
"llm_base_url": "https://openrouter.ai/api/v1",
|
||||||
|
"llm_model": "qwen/qwen3.5-9b",
|
||||||
|
"bank_id": "hermes",
|
||||||
|
"recall_budget": "low",
|
||||||
|
"recall_prefetch_method": "recall",
|
||||||
|
"auto_recall": true,
|
||||||
|
"recall_types": "observation",
|
||||||
|
"auto_retain": true,
|
||||||
|
"retain_async": true,
|
||||||
|
"retain_every_n_turns": 5,
|
||||||
|
"memory_mode": "hybrid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Function: `generate_cron_env_config`**
|
||||||
|
Ensure cron has the required env vars by running:
|
||||||
|
- `hermes config set cron.env.JIRA_EMAIL "${JIRA_EMAIL}"`
|
||||||
|
- `hermes config set cron.env.JIRA_API_TOKEN "${JIRA_API_TOKEN}"`
|
||||||
|
|
||||||
|
**Definition order:** The functions are defined first, then called from the main execution block at the bottom of the script.
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Reference D-10 for each section it creates/updates.
|
||||||
|
- If the baseline config.yaml structure changes (e.g., Hermes adds a new required field), the Python YAML script should preserve unknown keys (load, modify, dump — not overwrite completely).
|
||||||
|
- After writing config.yaml, validate with `hermes config get terminal.docker_image` to confirm it was set.
|
||||||
|
- Set `chmod 600` on .env immediately after writing (T-09-06).
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>grep -c 'generate_config_yaml\|generate_env_file\|generate_hindsight_config' /Users/bapung/Razer/ngn-agent/setup-ngn-agent.sh</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- `generate_config_yaml` sets all config.yaml keys using hermes config set + Python YAML
|
||||||
|
- `generate_env_file` writes all secrets and DEFAULT_REPOS to .env with chmod 600
|
||||||
|
- `generate_hindsight_config` writes hindsight/config.json
|
||||||
|
- Docker image reference in config.yaml is updated to ngn-agent:latest (D-10)
|
||||||
|
- Cron env vars (JIRA_EMAIL, JIRA_API_TOKEN) are configured
|
||||||
|
- D-10 referenced in comments for each section
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
<task type="auto">
|
||||||
|
<name>Task 3: Implement file/cron setup — scripts, skills, cron registration, gateway restart</name>
|
||||||
|
<files>ngn-agent/setup-ngn-agent.sh</files>
|
||||||
|
<action>
|
||||||
|
Append the file writing functions and cron registration to `setup-ngn-agent.sh`. These complete the setup script.
|
||||||
|
|
||||||
|
**Function: `write_session_init_script`**
|
||||||
|
Write `~/.hermes/scripts/session-init.sh` with the exact content from the current file (37 lines). This script verifies DEFAULT_REPOS mounts at session start. Use a heredoc with a quoted delimiter (`'EOF'`) to prevent variable expansion:
|
||||||
|
```bash
|
||||||
|
cat > ~/.hermes/scripts/session-init.sh << 'SCRIPT'
|
||||||
|
#!/bin/bash
|
||||||
|
# session-init.sh — Verify DEFAULT_REPOS mounts at session start
|
||||||
|
# ... (exact content)
|
||||||
|
SCRIPT
|
||||||
|
chmod +x ~/.hermes/scripts/session-init.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
**Function: `write_archive_script`**
|
||||||
|
Same approach for `~/.hermes/scripts/archive-stale-sessions.sh` (41 lines) with `DRY_RUN=true` default and `set -euo pipefail`.
|
||||||
|
|
||||||
|
**Function: `write_skill_files`**
|
||||||
|
For each of the 5 skills (per D-10: all 5 skill directories), create the directory and write the SKILL.md:
|
||||||
|
1. `~/.hermes/skills/ngn-agent/jira/SKILL.md`
|
||||||
|
2. `~/.hermes/skills/ngn-agent/aws-diagnostics/SKILL.md`
|
||||||
|
3. `~/.hermes/skills/ngn-agent/confluence/SKILL.md`
|
||||||
|
4. `~/.hermes/skills/ngn-agent/bitbucket/SKILL.md`
|
||||||
|
5. `~/.hermes/skills/ngn-agent/session/SKILL.md`
|
||||||
|
|
||||||
|
Use heredocs with quoted delimiters to write each file with exact content from the current source files. For `aws-diagnostics` and `session` skills, also create their reference directories and files:
|
||||||
|
- `~/.hermes/skills/ngn-agent/aws-diagnostics/references/multiregional-patterns.md`
|
||||||
|
- `~/.hermes/skills/ngn-agent/session/references/operational-monitoring.md`
|
||||||
|
|
||||||
|
**Function: `register_cron_jobs`**
|
||||||
|
Register 3 cron jobs (D-10) using `hermes cron create` with the exact syntax verified in Phase 8:
|
||||||
|
|
||||||
|
1. `ngn-daily-report` (per Phase 8 Plan 2 summary):
|
||||||
|
```
|
||||||
|
hermes cron create --deliver telegram --skill session --skill jira-query \
|
||||||
|
'0 9 * * *' \
|
||||||
|
'Daily session report. Export sessions, find active ones, check Jira, compose Telegram summary.'
|
||||||
|
```
|
||||||
|
Note: Use the exact cron registration pattern from Phase 8 SUMMARYs:
|
||||||
|
- Skill-backed: `--deliver telegram --skill <name> 'schedule' 'prompt'`
|
||||||
|
- The `ngn-weekly-stale-summary` and `ngn-weekly-archive` from Phase 8 Plan 2
|
||||||
|
|
||||||
|
2. `ngn-weekly-stale-summary`:
|
||||||
|
```
|
||||||
|
hermes cron create --deliver telegram --skill session \
|
||||||
|
'0 20 * * 0' \
|
||||||
|
'Weekly stale session summary. ...'
|
||||||
|
```
|
||||||
|
|
||||||
|
3. `ngn-weekly-archive`:
|
||||||
|
```
|
||||||
|
hermes cron create --no-agent --script archive-stale-sessions.sh \
|
||||||
|
'5 20 * * 0'
|
||||||
|
```
|
||||||
|
|
||||||
|
**Function: `offer_gateway_restart`**
|
||||||
|
At the end of the script, after all configuration is complete (per CONTEXT.md "Specific Ideas"):
|
||||||
|
```bash
|
||||||
|
echo ""
|
||||||
|
echo "==> Setup complete!"
|
||||||
|
echo ""
|
||||||
|
read -p "Restart Hermes gateway now? [Y/n]: " restart
|
||||||
|
if [[ "$restart" =~ ^[Yy]?$ ]]; then
|
||||||
|
hermes gateway restart
|
||||||
|
echo " → Gateway restarted."
|
||||||
|
else
|
||||||
|
echo " → Skipped. Run 'hermes gateway restart' when ready."
|
||||||
|
fi
|
||||||
|
```
|
||||||
|
|
||||||
|
**Main execution block:**
|
||||||
|
At the bottom of the script, add the main execution flow that calls all functions in order:
|
||||||
|
1. Parse arguments (Task 1)
|
||||||
|
2. Check prerequisites (Task 1)
|
||||||
|
3. Prompt for secrets (Task 1)
|
||||||
|
4. Create directories (Task 1)
|
||||||
|
5. Backup existing config (Task 1)
|
||||||
|
6. Generate config.yaml (Task 2)
|
||||||
|
7. Generate .env (Task 2)
|
||||||
|
8. Generate hindsight config (Task 2)
|
||||||
|
9. Generate cron env config (Task 2)
|
||||||
|
10. Write session-init script (Task 3)
|
||||||
|
11. Write archive script (Task 3)
|
||||||
|
12. Write skill files (Task 3)
|
||||||
|
13. Register cron jobs (Task 3)
|
||||||
|
14. Offer gateway restart (Task 3)
|
||||||
|
|
||||||
|
Each step should print a progress indicator like `[1/14] Checking prerequisites...`.
|
||||||
|
|
||||||
|
**Error handling:**
|
||||||
|
- If any step fails (non-zero exit), print a warning but continue (best-effort) unless it's a critical prerequisite.
|
||||||
|
- The `set -euo pipefail` at the top means any failure exits unless explicitly handled with `|| true`.
|
||||||
|
- Wrap non-critical steps: `register_cron_jobs || echo " ⚠ Cron registration had issues (may already exist)"`.
|
||||||
|
|
||||||
|
**Notes:**
|
||||||
|
- Reference D-10 for the full list of what the script creates/updates.
|
||||||
|
- Add a lifecycle comment at the top: "Embedded file snapshots frozen at: $(date +%Y-%m-%d) — Regenerate by re-running this phase."
|
||||||
|
- Test the script after writing: if Docker is running, do a dry-run check with `bash -n setup-ngn-agent.sh` to validate syntax.
|
||||||
|
</action>
|
||||||
|
<verify>
|
||||||
|
<automated>bash -n /Users/bapung/Razer/ngn-agent/setup-ngn-agent.sh</automated>
|
||||||
|
</verify>
|
||||||
|
<done>
|
||||||
|
- All 5 skill SKILL.md files are embedded as heredocs
|
||||||
|
- 2 reference files (multiregional-patterns.md, operational-monitoring.md) are embedded
|
||||||
|
- 2 scripts (session-init.sh, archive-stale-sessions.sh) are embedded and made executable
|
||||||
|
- 3 cron jobs are registered via hermes cron create
|
||||||
|
- Gateway restart offer is at the end
|
||||||
|
- Script passes `bash -n` syntax validation
|
||||||
|
- D-10 is referenced in comments for each section it creates/updates
|
||||||
|
- Script is fully self-contained (no external file read dependencies)
|
||||||
|
</done>
|
||||||
|
</task>
|
||||||
|
|
||||||
|
</tasks>
|
||||||
|
|
||||||
|
<threat_model>
|
||||||
|
## Trust Boundaries
|
||||||
|
|
||||||
|
| Boundary | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| Terminal (stdin) → script | User types secrets via read -s — masked input, no echo |
|
||||||
|
| Script → ~/.hermes/.env | Secrets written to plaintext file at rest |
|
||||||
|
| Script → Hermes cron DB | Cron job registration via hermes CLI |
|
||||||
|
| Script → ~/.hermes/config.yaml | Config YAML generation |
|
||||||
|
|
||||||
|
## STRIDE Threat Register
|
||||||
|
|
||||||
|
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|
||||||
|
|-----------|----------|-----------|-------------|-----------------|
|
||||||
|
| T-09-05 | Information Disclosure | Secret exposure in terminal history | mitigate | `read -s` for all secret prompts (masked input, no echo to terminal) |
|
||||||
|
| T-09-06 | Information Disclosure | ~/.hermes/.env world-readable secrets | mitigate | `chmod 600` on .env immediately after writing |
|
||||||
|
| T-09-07 | Tampering | Config file corruption from partial write | mitigate | Backup existing config.yaml to `.bak.<timestamp>` before modification |
|
||||||
|
| T-09-08 | Information Disclosure | Secrets captured in terminal scrollback | accept | Users are responsible for terminal security on their own machines |
|
||||||
|
| T-09-09 | Tampering | Cron job with malicious prompt | accept | Cron prompts are embedded in the setup script, not user-controllable |
|
||||||
|
|
||||||
|
## Package Legitimacy Gate
|
||||||
|
|
||||||
|
| Package | Registry | Verdict | Disposition |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| (none) | — | — | No npm/pip/cargo packages installed. All operations via bash built-ins, hermes CLI, and Python stdlib. |
|
||||||
|
</threat_model>
|
||||||
|
|
||||||
|
<verification>
|
||||||
|
- Automated: `bash -n /Users/bapung/Razer/ngn-agent/setup-ngn-agent.sh` passes (syntax check)
|
||||||
|
- Automated: `grep -c 'hermes cron create' setup-ngn-agent.sh` returns 3 (all cron jobs)
|
||||||
|
- Automated: `grep -c 'write_session_init\|write_archive_script\|write_jira_skill\|write_aws_skill\|write_confluence_skill\|write_bitbucket_skill\|write_session_skill' setup-ngn-agent.sh` returns 8 (all scripts + 5 skills + 2 ref files)
|
||||||
|
- Manual: User can inspect `setup-ngn-agent.sh --help` to see all parameters
|
||||||
|
- Dry-run: User can run `bash -x setup-ngn-agent.sh` to trace execution (no destructive test needed)
|
||||||
|
</verification>
|
||||||
|
|
||||||
|
<success_criteria>
|
||||||
|
1. setup-ngn-agent.sh exists, is executable, and passes bash syntax validation
|
||||||
|
2. Script accepts all 9 parameters via getopts with documented defaults
|
||||||
|
3. Prerequisites checks for Hermes CLI, Docker, SSH keys
|
||||||
|
4. All 4 secrets prompted with masked input
|
||||||
|
5. Config generation: config.yaml, .env, hindsight/config.json
|
||||||
|
6. All 5 skill directories + SKILL.md files + reference files embedded and written
|
||||||
|
7. Both scripts (session-init.sh, archive-stale-sessions.sh) embedded and written
|
||||||
|
8. All 3 cron jobs registered (ngn-daily-report, ngn-weekly-stale-summary, ngn-weekly-archive)
|
||||||
|
9. Docker image reference updated to ngn-agent:latest
|
||||||
|
10. Existing config.yaml backed up before modification
|
||||||
|
11. Gateway restart offered at completion
|
||||||
|
12. D-06 through D-10 referenced in comments for traceability
|
||||||
|
</success_criteria>
|
||||||
|
</objective>
|
||||||
167
.planning/phases/09-tooling-portable-setup/09-02-SUMMARY.md
Normal file
167
.planning/phases/09-tooling-portable-setup/09-02-SUMMARY.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
---
|
||||||
|
phase: 09-tooling-portable-setup
|
||||||
|
plan: 02
|
||||||
|
subsystem: setup
|
||||||
|
tags: [bash, hermes, setup-script, provisioning, cron, skills]
|
||||||
|
|
||||||
|
requires:
|
||||||
|
- phase: 08-cron-reporting
|
||||||
|
plan: 01
|
||||||
|
provides: archive-stale-sessions.sh script content, cron registration patterns (hermes cron create CLI syntax, DRY_RUN pattern)
|
||||||
|
- phase: 08-cron-reporting
|
||||||
|
plan: 02
|
||||||
|
provides: Weekly cron job patterns (ngn-weekly-stale-summary, ngn-weekly-archive) with 5-minute offset between summary and archive
|
||||||
|
- phase: 07-main-session-skill
|
||||||
|
provides: session skill SKILL.md content for embedding; hindsight_retain templates, Jira ticket creation pattern with epic cache
|
||||||
|
- phase: 04-jira-skill
|
||||||
|
provides: jira-query skill SKILL.md content for embedding
|
||||||
|
|
||||||
|
provides:
|
||||||
|
- ngn-agent/setup-ngn-agent.sh — 1340-line standalone portable setup script with embedded file snapshots
|
||||||
|
- All 5 skill files + 2 reference files embedded as heredocs for self-contained re-creation
|
||||||
|
- Config generation: config.yaml (via hermes config set + Python yaml), .env (with chmod 600), hindsight/config.json
|
||||||
|
- 3 cron job registrations (ngn-daily-report, ngn-weekly-stale-summary, ngn-weekly-archive)
|
||||||
|
- Interactive secret prompts with masked input (T-09-05 mitigation)
|
||||||
|
- Best-effort error handling pattern for non-critical steps
|
||||||
|
|
||||||
|
affects:
|
||||||
|
- Phase 10+ (any phase needing to regenerate ngn-agent configuration on a new machine)
|
||||||
|
|
||||||
|
tech-stack:
|
||||||
|
added: []
|
||||||
|
patterns:
|
||||||
|
- Standalone portable setup script with embedded heredocs (no external file dependencies)
|
||||||
|
- Hybrid config generation: hermes config set for scalars + Python yaml (fallback sed) for arrays
|
||||||
|
- prompt_secret function with env-var skip, masked input, loop-until-nonempty
|
||||||
|
- Best-effort error handling: non-critical steps wrapped with || true
|
||||||
|
- Progress indicator pattern [N/14] for step-by-step execution
|
||||||
|
|
||||||
|
key-files:
|
||||||
|
created:
|
||||||
|
- ngn-agent/setup-ngn-agent.sh — 1340-line portable setup script
|
||||||
|
modified: []
|
||||||
|
|
||||||
|
key-decisions:
|
||||||
|
- "Each embedded file is a separate write function (write_session_init, write_jira_skill, etc.) — self-documenting and independently testable"
|
||||||
|
- "Config.yaml uses hybrid approach: hermes config set for scalar keys, Python yaml module for arrays, sed fallback when yaml unavailable"
|
||||||
|
- "Skill and script files embedded as heredocs with quoted delimiters ('EOF') — prevents variable expansion at script generation time"
|
||||||
|
- "Cron registration wrapped with 2>/dev/null + 'may already exist' message — idempotent re-runs"
|
||||||
|
- "Non-interactive mode (-y) reads secrets from environment variables with : ${VAR:?error} validation"
|
||||||
|
|
||||||
|
patterns-established:
|
||||||
|
- "Standalone portable setup script pattern: argument parsing → prereq checks → secret prompts → file generation → cron registration → restart offer"
|
||||||
|
- "Embedded heredoc pattern: each file is a bash function with cat > path << 'QUOTED_DELIM' to prevent variable expansion in embedded content"
|
||||||
|
- "Hybrid YAML generation: hermes config set for scalars, Python yaml for arrays, sed fallback"
|
||||||
|
|
||||||
|
requirements-completed: [SETUP-01]
|
||||||
|
|
||||||
|
duration: 4 min
|
||||||
|
completed: 2026-06-15
|
||||||
|
---
|
||||||
|
|
||||||
|
# Phase 9 Plan 2: Portable ngn-agent Setup Script Summary
|
||||||
|
|
||||||
|
**Portable 1340-line standalone bash setup script (ngn-agent/setup-ngn-agent.sh) for recreating all ngn-agent Hermes configuration on a fresh macOS machine — argument parsing, interactive secrets, config YAML/env/hindsight generation, all 5 skills + 2 scripts embedded, 3 cron jobs registered, gateway restart offer**
|
||||||
|
|
||||||
|
## Performance
|
||||||
|
|
||||||
|
- **Duration:** 4 min
|
||||||
|
- **Started:** 2026-06-15 23:26 +08
|
||||||
|
- **Completed:** 2026-06-15 23:30 +08
|
||||||
|
- **Tasks:** 3
|
||||||
|
- **Files modified:** 1 (1340 lines added)
|
||||||
|
|
||||||
|
## Accomplishments
|
||||||
|
|
||||||
|
- Created `setup-ngn-agent.sh` (1340 lines) — fully self-contained, no external file dependencies
|
||||||
|
- Argument parsing with getopts for 9 configurable parameters (SSH keys, repo paths, timezone, docker image)
|
||||||
|
- Prerequisite validation: Hermes CLI on PATH, Docker running, SSH key file existence, repo path existence
|
||||||
|
- Interactive masked secret prompts for JIRA_API_TOKEN, JIRA_EMAIL, TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY (T-09-05)
|
||||||
|
- Config generation via hermes config set (scalars) + Python yaml (arrays) + sed fallback
|
||||||
|
- .env generation with chmod 600 permissions (T-09-06)
|
||||||
|
- hindsight/config.json with local_embedded mode, qwen/qwen3.5-9b model, hybrid memory
|
||||||
|
- All 5 skills (jira, aws-diagnostics, confluence, bitbucket, session) + 2 reference files embedded as heredocs
|
||||||
|
- Both scripts (session-init.sh, archive-stale-sessions.sh) embedded as heredocs with executable permission
|
||||||
|
- 3 cron jobs registered with proper schedules and delivery methods
|
||||||
|
- Config.yaml backup before modification (T-09-07)
|
||||||
|
- Gateway restart offer at completion
|
||||||
|
- Non-interactive mode (-y) for automated provisioning
|
||||||
|
|
||||||
|
## Task Commits
|
||||||
|
|
||||||
|
Each task was committed atomically:
|
||||||
|
|
||||||
|
1. **Task 1: Create setup script skeleton — args, prereqs, and interactive prompts** - `2de51b1` (feat)
|
||||||
|
2. **Task 2: Implement config generation — config.yaml, .env, hindsight/config.json** - `9da9728` (feat)
|
||||||
|
3. **Task 3: Implement file/cron setup — scripts, skills, cron registration, gateway restart** - `5a8c183` (feat)
|
||||||
|
|
||||||
|
## Files Created/Modified
|
||||||
|
|
||||||
|
- `ngn-agent/setup-ngn-agent.sh` — 1340-line standalone portable setup script (created)
|
||||||
|
|
||||||
|
## Decisions Made
|
||||||
|
|
||||||
|
- **Embedded heredocs over base64**: All skill files, scripts, and reference files are embedded using heredocs with quoted delimiters (`'EOF'`) — prevents variable expansion at script generation time, keeps content human-readable without encoding overhead. Each file is a separate bash function for modularity and independent testing.
|
||||||
|
|
||||||
|
- **Hybrid config.yaml generation**: `hermes config set` for scalar keys (docker_image, memory.provider, timezone, etc.) + Python yaml module for arrays (docker_volumes, shell_init_files, docker_forward_env). Python's yaml.safe_load → modify → yaml.dump preserves unknown keys and maintains proper YAML formatting. Fallback to sed-based injection if the `yaml` Python module is unavailable.
|
||||||
|
|
||||||
|
- **Best-effort error handling**: The script uses `set -euo pipefail` for strict mode, but non-critical steps like cron registration are wrapped with `|| echo "⚠ ..."` — allowing the script to continue if a cron job already exists or if the Hermes CLI returns a non-fatal error.
|
||||||
|
|
||||||
|
- **Prompt_secret with env-var skip**: Secret prompts check if the environment variable is already set before prompting (supports pre-exported secrets or non-interactive mode via `-y` flag).
|
||||||
|
|
||||||
|
- **Cron job registration order**: Daily report first (most critical), then weekly summary, then weekly archive. Archive runs 5 minutes after summary (20:05 vs 20:00) per Phase 8 pattern to prevent race conditions.
|
||||||
|
|
||||||
|
## Deviations from Plan
|
||||||
|
|
||||||
|
None — plan executed exactly as written.
|
||||||
|
|
||||||
|
## Issues Encountered
|
||||||
|
|
||||||
|
None. All three tasks completed cleanly with syntax validation passing (`bash -n`).
|
||||||
|
|
||||||
|
## User Setup Required
|
||||||
|
|
||||||
|
**External services require manual configuration.** See [09-USER-SETUP.md](./09-USER-SETUP.md) for:
|
||||||
|
- Hermes CLI v0.16+ installation (pre-requisite)
|
||||||
|
- JIRA_API_TOKEN from https://id.atlassian.com/manage/api-tokens
|
||||||
|
- TELEGRAM_BOT_TOKEN from https://t.me/BotFather
|
||||||
|
- OPENROUTER_API_KEY from https://openrouter.ai/keys
|
||||||
|
|
||||||
|
The setup script handles interactive prompting for all 4 secrets.
|
||||||
|
|
||||||
|
## Threat Flags
|
||||||
|
|
||||||
|
None — all threat mitigations from the plan's threat model are satisfied:
|
||||||
|
- T-09-05 (Information Disclosure — secret exposure in terminal history): Mitigated — `read -s` for all secret prompts with masked input.
|
||||||
|
- T-09-06 (Information Disclosure — .env world-readable secrets): Mitigated — `chmod 600` on .env immediately after writing.
|
||||||
|
- T-09-07 (Tampering — config file corruption): Mitigated — backup existing config.yaml to `.bak.<timestamp>` before modification.
|
||||||
|
- T-09-08 (Information Disclosure — terminal scrollback): Accepted — users responsible for terminal security.
|
||||||
|
- T-09-09 (Tampering — cron prompt injection): Accepted — prompts embedded in setup script, not user-controllable.
|
||||||
|
|
||||||
|
## Next Phase Readiness
|
||||||
|
|
||||||
|
- Phase 9 Plan 1 (Docker custom image) complete, Plan 2 (setup script) complete
|
||||||
|
- Phase 9 fully complete — both Dockerfile + build script and portable setup script artifacts ready
|
||||||
|
- User can run `setup-ngn-agent.sh --help` to see all parameters
|
||||||
|
- Dry-run: user can run `bash -x setup-ngn-agent.sh` to trace execution (no destructive test needed)
|
||||||
|
- Ready for next phase (v1.1 milestone completion, validation, or v1.2 planning)
|
||||||
|
|
||||||
|
## Self-Check: PASSED
|
||||||
|
|
||||||
|
| Check | Result |
|
||||||
|
|-------|--------|
|
||||||
|
| File exists | ✓ setup-ngn-agent.sh (1340 lines) |
|
||||||
|
| File executable | ✓ |
|
||||||
|
| Syntax validation (`bash -n`) | ✓ |
|
||||||
|
| Task 1 commit (2de51b1) | ✓ |
|
||||||
|
| Task 2 commit (9da9728) | ✓ |
|
||||||
|
| Task 3 commit (5a8c183) | ✓ |
|
||||||
|
| Script ≥ 300 lines | ✓ (1340 lines) |
|
||||||
|
| `hermes cron create` count = 3 | ✓ |
|
||||||
|
| Function names ≥ 8 matched | ✓ (14 matches) |
|
||||||
|
| SUMMARY.md exists | ✓ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 09-tooling-portable-setup*
|
||||||
|
*Completed: 2026-06-15*
|
||||||
130
.planning/phases/09-tooling-portable-setup/09-CONTEXT.md
Normal file
130
.planning/phases/09-tooling-portable-setup/09-CONTEXT.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Phase 9: Tooling & Portable Setup - Context
|
||||||
|
|
||||||
|
**Gathered:** 2026-06-14
|
||||||
|
**Status:** Ready for planning
|
||||||
|
|
||||||
|
<domain>
|
||||||
|
## Phase Boundary
|
||||||
|
|
||||||
|
Create a custom Docker image with essential platform engineering tools and a portable setup script that can provision a fresh macOS machine with all ngn-agent configuration in a single invocation.
|
||||||
|
|
||||||
|
**In scope:** Custom Dockerfile in project repo, image build tooling, parameterized setup script covering all config, file creation, volume mounts, skills, and cron jobs
|
||||||
|
|
||||||
|
**Out of scope:** Installing Hermes Agent itself (assumes Hermes v0.16+ is installed), cloud-native deployment (macOS-only setup), multi-architecture image builds
|
||||||
|
</domain>
|
||||||
|
|
||||||
|
<decisions>
|
||||||
|
## Implementation Decisions
|
||||||
|
|
||||||
|
### Custom Docker Image
|
||||||
|
- **D-01:** Dockerfile lives in this repo at `ngn-agent/docker/Dockerfile` — extends `nikolaik/python-nodejs:python3.11-nodejs20`
|
||||||
|
- **D-02:** Pin specific tool versions — Dockerfile should specify exact versions for reproducibility
|
||||||
|
- **D-03:** Tools to include:
|
||||||
|
- **aws-cli**: v2 (latest stable)
|
||||||
|
- **terraform**: latest stable
|
||||||
|
- **helm**: latest stable
|
||||||
|
- **kubectl**: latest stable matching cluster version
|
||||||
|
- **datadog CLI** (`pup`): latest stable
|
||||||
|
- **D-04:** Build script at `ngn-agent/docker/build.sh` — single command to build the image
|
||||||
|
- **D-05:** Image tag: `ngn-agent:latest` (local only, no registry push)
|
||||||
|
|
||||||
|
### Portable Setup Script
|
||||||
|
- **D-06:** Single script at `ngn-agent/setup-ngn-agent.sh` — recreates all configuration on a fresh machine
|
||||||
|
- **D-07:** Assumes Hermes v0.16+ is already installed and `hermes` CLI is on PATH
|
||||||
|
- **D-08:** Interactive prompts for all secrets:
|
||||||
|
- `JIRA_API_TOKEN` (required for Atlassian integrations)
|
||||||
|
- `JIRA_EMAIL` (required for Atlassian integrations)
|
||||||
|
- `TELEGRAM_BOT_TOKEN` (required for gateway)
|
||||||
|
- `OPENROUTER_API_KEY` (if not already set)
|
||||||
|
- **D-09:** Configurable parameters (supplied via args or prompts):
|
||||||
|
- SSH key paths (default: `~/.ssh/id_ed25519razer`, `~/.ssh/id_rsa`)
|
||||||
|
- SSH config path (default: `~/.ssh/config`)
|
||||||
|
- SSH known_hosts path (default: `~/.ssh/known_hosts`)
|
||||||
|
- Repo paths (default: `~/Razer/rai-ops`, `~/Razer/rai-deployment`, `~/Razer/rai-devtools`)
|
||||||
|
- Timezone (default: `Asia/Singapore`)
|
||||||
|
- **D-10:** What the setup script creates/updates:
|
||||||
|
- `~/.hermes/config.yaml` — docker_volumes (SSH + repo mounts), shell_init_files, docker_forward_env, cron config
|
||||||
|
- `~/.hermes/.env` — secrets and DEFAULT_REPOS
|
||||||
|
- `~/.hermes/hindsight/config.json` — Hindsight config
|
||||||
|
- `~/.hermes/scripts/session-init.sh` — mount verification script
|
||||||
|
- `~/.hermes/scripts/archive-stale-sessions.sh` — archive script
|
||||||
|
- `~/.hermes/skills/ngn-agent/` — all 5 skill directories
|
||||||
|
- `~/.hermes/archive/sessions/` — archive directory
|
||||||
|
- Register 3 cron jobs (ngn-daily-report, ngn-weekly-stale-summary, ngn-weekly-archive)
|
||||||
|
- Update Docker image reference in config.yaml
|
||||||
|
|
||||||
|
### the agent's Discretion
|
||||||
|
- **Dockerfile tool version selection**: Choose stable versions current at time of implementation
|
||||||
|
- **Setup script structure**: Interactive prompt flow, output formatting, error handling approach
|
||||||
|
- **Config file templates**: How to generate config.yaml sections, .env format, etc.
|
||||||
|
</decisions>
|
||||||
|
|
||||||
|
<canonical_refs>
|
||||||
|
## Canonical References
|
||||||
|
|
||||||
|
**Downstream agents MUST read these before planning or implementing.**
|
||||||
|
|
||||||
|
### Project Repo
|
||||||
|
- `.planning/REQUIREMENTS.md` §TOOL-01, SETUP-01 — Requirement definitions
|
||||||
|
- `.planning/ROADMAP.md` §Phase 9 — Phase goal and success criteria
|
||||||
|
|
||||||
|
### Current Configuration (what the setup script must recreate)
|
||||||
|
- `~/.hermes/config.yaml` — Full config with docker_volumes, shell_init_files, docker_forward_env, cron, memory.provider, telegram
|
||||||
|
- `~/.hermes/.env` — All env vars (JIRA_API_TOKEN, JIRA_EMAIL, TELEGRAM_BOT_TOKEN, OPENROUTER_API_KEY, DEFAULT_REPOS, HINDSIGHT_LLM_API_KEY)
|
||||||
|
- `~/.hermes/hindsight/config.json` — Hindsight local_embedded config with all settings
|
||||||
|
- `~/.hermes/scripts/session-init.sh` — Mount verification script
|
||||||
|
- `~/.hermes/scripts/archive-stale-sessions.sh` — Session archive script
|
||||||
|
- `~/.hermes/skills/ngn-agent/` — 5 skill files (aws-diagnostics, jira, confluence, bitbucket, session)
|
||||||
|
- `.planning/phases/08-cron-reporting/08-01-SUMMARY.md` — Cron job registrations
|
||||||
|
- `.planning/phases/08-cron-reporting/08-02-SUMMARY.md` — Weekly cron job details
|
||||||
|
|
||||||
|
### Base Image
|
||||||
|
- `nikolaik/python-nodejs:python3.11-nodejs20` — Current Hermes Docker image
|
||||||
|
|
||||||
|
### Existing Skills (templates for setup script)
|
||||||
|
- `~/.hermes/skills/ngn-agent/aws-diagnostics/SKILL.md`
|
||||||
|
- `~/.hermes/skills/ngn-agent/jira/SKILL.md`
|
||||||
|
- `~/.hermes/skills/ngn-agent/confluence/SKILL.md`
|
||||||
|
- `~/.hermes/skills/ngn-agent/bitbucket/SKILL.md`
|
||||||
|
- `~/.hermes/skills/ngn-agent/session/SKILL.md`
|
||||||
|
</canonical_refs>
|
||||||
|
|
||||||
|
<code_context>
|
||||||
|
## Existing Code Insights
|
||||||
|
|
||||||
|
### Reusable Assets
|
||||||
|
- **Hermes config.yaml** — Current state is the source of truth for what setup script must recreate
|
||||||
|
- **Skill files** — 5 existing SKILL.md files that setup script must copy into place
|
||||||
|
- **Script files** — session-init.sh and archive-stale-sessions.sh that setup script must copy
|
||||||
|
- **Hindsight config.json** — Current config that setup script must create
|
||||||
|
|
||||||
|
### Integration Points
|
||||||
|
- `ngn-agent/docker/Dockerfile` — New file (create)
|
||||||
|
- `ngn-agent/docker/build.sh` — New file (create)
|
||||||
|
- `ngn-agent/setup-ngn-agent.sh` — New file (create)
|
||||||
|
- `~/.hermes/config.yaml` — Modified by setup script
|
||||||
|
- `~/.hermes/.env` — Created by setup script with user-provided secrets
|
||||||
|
</code_context>
|
||||||
|
|
||||||
|
<specifics>
|
||||||
|
## Specific Ideas
|
||||||
|
|
||||||
|
- The setup script should use `hermes config set` where possible instead of raw YAML editing
|
||||||
|
- Secrets should be prompted interactively with masked input where the terminal supports it
|
||||||
|
- The setup script should validate prerequisites (Hermes installed, Docker running, SSH keys exist) before making changes
|
||||||
|
- The Dockerfile should install tools via apt-get and pip where possible, with version pinning via checksums or apt version strings
|
||||||
|
- After setup, script should offer to restart the Hermes gateway
|
||||||
|
</specifics>
|
||||||
|
|
||||||
|
<deferred>
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Multi-architecture image builds (arm64 + amd64) — defer until needed
|
||||||
|
- Cloud-native deployment (Docker Compose, Fly.io, etc.) — out of scope
|
||||||
|
- CI/CD for image builds — out of scope
|
||||||
|
</deferred>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Phase: 9-Tooling & Portable Setup*
|
||||||
|
*Context gathered: 2026-06-14*
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
# Phase 9: Tooling & Portable Setup - Discussion Log
|
||||||
|
|
||||||
|
> **Audit trail only.**
|
||||||
|
|
||||||
|
**Date:** 2026-06-14
|
||||||
|
**Phase:** 9-Tooling & Portable Setup
|
||||||
|
**Areas discussed:** Custom Docker image, Setup script approach
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Custom Docker Image
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| In this repo | Dockerfile in ngn-agent repo, pin tool versions | ✓ |
|
||||||
|
| Dedicated repo | Separate repo for image build | |
|
||||||
|
|
||||||
|
**User's choice:** In this repo, pin tool versions
|
||||||
|
**Notes:** Dockerfile at `ngn-agent/docker/Dockerfile`, build script at `ngn-agent/docker/build.sh`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Setup Script Approach
|
||||||
|
|
||||||
|
| Option | Description | Selected |
|
||||||
|
|--------|-------------|----------|
|
||||||
|
| Full setup + config | All config, secrets prompted, directory creation, volume mounts, skills, cron | ✓ |
|
||||||
|
| Config only | Just copy config files | |
|
||||||
|
|
||||||
|
**User's choice:** Full setup. Secrets specified during setup with interactive prompts.
|
||||||
|
**Notes:** Script creates everything. Assumes Hermes installed. Interactive prompts for secrets.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## the agent's Discretion
|
||||||
|
|
||||||
|
- Dockerfile tool version selection
|
||||||
|
- Setup script interactive flow design
|
||||||
|
- Config file template generation
|
||||||
|
|
||||||
|
## Deferred Ideas
|
||||||
|
|
||||||
|
- Multi-architecture builds
|
||||||
|
- Cloud-native deployment
|
||||||
|
- CI/CD for images
|
||||||
607
.planning/phases/09-tooling-portable-setup/09-RESEARCH.md
Normal file
607
.planning/phases/09-tooling-portable-setup/09-RESEARCH.md
Normal file
@@ -0,0 +1,607 @@
|
|||||||
|
# Phase 9: Tooling & Portable Setup — Research
|
||||||
|
|
||||||
|
**Researched:** 2026-06-15
|
||||||
|
**Domain:** Docker image build, portable bash setup script, Hermes config management
|
||||||
|
**Confidence:** HIGH (verified via official sources for all tool versions and installation methods)
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
Phase 9 has two independent workstreams: (1) a custom Dockerfile extending `nikolaik/python-nodejs` with AWS CLI v2, Terraform, Helm, kubectl, and Datadog CLI (pup), and (2) a portable bash setup script that recreates all ngn-agent configuration on a fresh macOS machine. Both are new files living in the project repo — the Dockerfile at `docker/Dockerfile` with a build script at `docker/build.sh`, and the setup script at `setup-ngn-agent.sh`.
|
||||||
|
|
||||||
|
All five tools for the Docker image are installable on the Debian-based base image. AWS CLI v2 uses its official curl→unzip→install script (no apt repo for v2). Terraform and kubectl have official apt repos. Helm has a community-maintained Buildkite apt repo. Pup (Datadog CLI) is a Rust binary downloaded from GitHub releases. The base image tag `python3.11-nodejs20` **may no longer be available** — the maintainer has dropped Python 3.11 + Node.js 20 tags; the smallest Node.js version for Python 3.11 is now `python3.11-nodejs22`. The setup script should use `hermes config set` for individual key paths where possible, with `sed` as fallback for complex YAML structures.
|
||||||
|
|
||||||
|
**Primary recommendation:** Two plans — (1) Dockerfile + build script with pinned tool versions, (2) portable setup script with interactive prompt flow for secrets.
|
||||||
|
|
||||||
|
## Phase Requirements
|
||||||
|
|
||||||
|
| ID | Description | Research Support |
|
||||||
|
|----|-------------|------------------|
|
||||||
|
| TOOL-01 | Custom Hermes Docker image with aws-cli, terraform, helm, kubectl, datadog CLI | All five tools have documented installation methods for Debian-based images. Versions verified from official sources. |
|
||||||
|
| SETUP-01 | Portable setup-ngn-agent.sh script recreating all config | Current config.yaml (565 lines), .env (484 lines), hindsight/config.json, 2 scripts, 5 skills, and 3 cron jobs all identified. |
|
||||||
|
|
||||||
|
## User Constraints (from CONTEXT.md)
|
||||||
|
|
||||||
|
<user_constraints>
|
||||||
|
### Locked Decisions
|
||||||
|
|
||||||
|
#### Custom Docker Image
|
||||||
|
- **D-01:** Dockerfile lives in this repo at `ngn-agent/docker/Dockerfile` — extends `nikolaik/python-nodejs:python3.11-nodejs20`
|
||||||
|
- **D-02:** Pin specific tool versions — Dockerfile should specify exact versions for reproducibility
|
||||||
|
- **D-03:** Tools to include:
|
||||||
|
- **aws-cli**: v2 (latest stable)
|
||||||
|
- **terraform**: latest stable
|
||||||
|
- **helm**: latest stable
|
||||||
|
- **kubectl**: latest stable matching cluster version
|
||||||
|
- **datadog CLI** (`pup`): latest stable
|
||||||
|
- **D-04:** Build script at `ngn-agent/docker/build.sh` — single command to build the image
|
||||||
|
- **D-05:** Image tag: `ngn-agent:latest` (local only, no registry push)
|
||||||
|
|
||||||
|
#### Portable Setup Script
|
||||||
|
- **D-06:** Single script at `ngn-agent/setup-ngn-agent.sh` — recreates all configuration on a fresh machine
|
||||||
|
- **D-07:** Assumes Hermes v0.16+ is already installed and `hermes` CLI is on PATH
|
||||||
|
- **D-08:** Interactive prompts for all secrets:
|
||||||
|
- `JIRA_API_TOKEN` (required for Atlassian integrations)
|
||||||
|
- `JIRA_EMAIL` (required for Atlassian integrations)
|
||||||
|
- `TELEGRAM_BOT_TOKEN` (required for gateway)
|
||||||
|
- `OPENROUTER_API_KEY` (if not already set)
|
||||||
|
- **D-09:** Configurable parameters (supplied via args or prompts):
|
||||||
|
- SSH key paths (default: `~/.ssh/id_ed25519razer`, `~/.ssh/id_rsa`)
|
||||||
|
- SSH config path (default: `~/.ssh/config`)
|
||||||
|
- SSH known_hosts path (default: `~/.ssh/known_hosts`)
|
||||||
|
- Repo paths (default: `~/Razer/rai-ops`, `~/Razer/rai-deployment`, `~/Razer/rai-devtools`)
|
||||||
|
- Timezone (default: `Asia/Singapore`)
|
||||||
|
- **D-10:** What the setup script creates/updates:
|
||||||
|
- `~/.hermes/config.yaml` — docker_volumes (SSH + repo mounts), shell_init_files, docker_forward_env, cron config
|
||||||
|
- `~/.hermes/.env` — secrets and DEFAULT_REPOS
|
||||||
|
- `~/.hermes/hindsight/config.json` — Hindsight config
|
||||||
|
- `~/.hermes/scripts/session-init.sh` — mount verification script
|
||||||
|
- `~/.hermes/scripts/archive-stale-sessions.sh` — archive script
|
||||||
|
- `~/.hermes/skills/ngn-agent/` — all 5 skill directories
|
||||||
|
- `~/.hermes/archive/sessions/` — archive directory
|
||||||
|
- Register 3 cron jobs (ngn-daily-report, ngn-weekly-stale-summary, ngn-weekly-archive)
|
||||||
|
- Update Docker image reference in config.yaml
|
||||||
|
|
||||||
|
### the agent's Discretion
|
||||||
|
- **Dockerfile tool version selection**: Choose stable versions current at time of implementation
|
||||||
|
- **Setup script structure**: Interactive prompt flow, output formatting, error handling approach
|
||||||
|
- **Config file templates**: How to generate config.yaml sections, .env format, etc.
|
||||||
|
|
||||||
|
### Deferred Ideas (OUT OF SCOPE)
|
||||||
|
- Multi-architecture image builds (arm64 + amd64) — defer until needed
|
||||||
|
- Cloud-native deployment (Docker Compose, Fly.io, etc.) — out of scope
|
||||||
|
- CI/CD for image builds — out of scope
|
||||||
|
</user_constraints>
|
||||||
|
|
||||||
|
## Architectural Responsibility Map
|
||||||
|
|
||||||
|
| Capability | Primary Tier | Secondary Tier | Rationale |
|
||||||
|
|------------|-------------|----------------|-----------|
|
||||||
|
| Docker image build | Developer Machine (macOS) | — | Builds locally, `docker build` runs on host |
|
||||||
|
| Tool installation in Dockerfile | Docker Image Build (CI/macOS) | — | Each `RUN` layer installs tool; version-pinned for reproducibility |
|
||||||
|
| Interactive secret prompts | Setup Script (macOS CLI) | — | `read -s` reads secrets from stdin, writes to ~/.hermes/.env |
|
||||||
|
| Config file generation | Setup Script (macOS CLI) | — | Creates/modifies ~/.hermes/config.yaml, .env, config.json |
|
||||||
|
| Cron job registration | Setup Script → Hermes CLI | — | Uses `hermes cron create` CLI commands |
|
||||||
|
| Skill file copying | Setup Script (macOS CLI) | — | Copies SKILL.md files from embedded base64 or repo |
|
||||||
|
|
||||||
|
## Standard Stack
|
||||||
|
|
||||||
|
### Core
|
||||||
|
|
||||||
|
| Tool | Method | Purpose | Installation Source |
|
||||||
|
|------|--------|---------|-------------------|
|
||||||
|
| Base Image | `FROM nikolaik/python-nodejs:python3.11-nodejs22` | Hermes-compatible Python + Node.js runtime | Docker Hub (official nikolaik image) |
|
||||||
|
| AWS CLI v2 | curl → unzip → `./aws/install` | AWS diagnostics via CLI | [Official docs](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) |
|
||||||
|
| Terraform | HashiCorp apt repo → `apt install terraform` | Infrastructure-as-code management | [HashiCorp Developer](https://developer.hashicorp.com/terraform/install#linux) |
|
||||||
|
| Helm | Buildkite apt repo → `apt install helm` | Kubernetes package management | [Helm docs](https://helm.sh/docs/intro/install/#from-apt-debianubuntu) |
|
||||||
|
| kubectl | Google Kubernetes apt repo → `apt install kubectl` | Kubernetes cluster management | [Kubernetes docs](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/) |
|
||||||
|
| Datadog CLI (pup) | curl binary from GitHub Releases | Datadog observability via CLI | [DataDog/pup releases](https://github.com/DataDog/pup/releases) |
|
||||||
|
|
||||||
|
### Pinned Versions (current as of 2026-06-15)
|
||||||
|
|
||||||
|
| Tool | Version | Source | Confidence |
|
||||||
|
|------|---------|--------|------------|
|
||||||
|
| AWS CLI v2 | 2.27.41 | [CITED: docs.aws.amazon.com/cli/](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) — version shown in output example | HIGH |
|
||||||
|
| Terraform | 1.15.6 | [CITED: developer.hashicorp.com/terraform/install](https://developer.hashicorp.com/terraform/install) | HIGH |
|
||||||
|
| Helm | 4.2.1 | [CITED: helm.sh/docs/intro/install](https://helm.sh/docs/intro/install/) + [github.com/helm/helm/releases/tag/v4.2.1](https://github.com/helm/helm/releases/tag/v4.2.1) | HIGH |
|
||||||
|
| kubectl | 1.36.1 | [CITED: kubernetes.io/releases](https://kubernetes.io/releases/) — latest stable | HIGH |
|
||||||
|
| Datadog CLI (pup) | 1.1.0 | [CITED: github.com/DataDog/pup/releases/tag/v1.1.0](https://github.com/DataDog/pup/releases/tag/v1.1.0) | HIGH |
|
||||||
|
|
||||||
|
### Base Image Tag Issue
|
||||||
|
|
||||||
|
**⚠ D-01 specifies `nikolaik/python-nodejs:python3.11-nodejs20` but this tag may no longer be available.** [VERIFIED: hub.docker.com/r/nikolaik/python-nodejs] The current tag table shows Python 3.11 is only available with Node.js 26, 24, or 22. The `nodejs20` tags were likely removed when Node.js 20 reached end of life (April 2026).
|
||||||
|
|
||||||
|
Available Python 3.11 tags (as of 2026-06-15):
|
||||||
|
- `python3.11-nodejs26` (Node.js 26.3.0, Debian trixie) — latest
|
||||||
|
- `python3.11-nodejs26-bookworm` (Node.js 26.3.0, Debian bookworm)
|
||||||
|
- `python3.11-nodejs26-slim`
|
||||||
|
- `python3.11-nodejs24` (Node.js 24.16.0, Debian trixie)
|
||||||
|
- `python3.11-nodejs24-bookworm` (Node.js 24.16.0, Debian bookworm)
|
||||||
|
- `python3.11-nodejs22` (Node.js 22.22.3, Debian trixie)
|
||||||
|
- `python3.11-nodejs22-bookworm` (Node.js 22.22.3, Debian bookworm)
|
||||||
|
|
||||||
|
**Recommendation:** Use `python3.11-nodejs22-bookworm` as a close match (Node.js 22 is still under LTS until Apr 2027). This needs user confirmation — flag for discuss-phase.
|
||||||
|
|
||||||
|
### Setup Script Standard Stack
|
||||||
|
|
||||||
|
| Library/Tool | Usage | Why |
|
||||||
|
|-------------|-------|-----|
|
||||||
|
| `bash` (built-in) | Script host | Zero dependencies, available on every macOS machine |
|
||||||
|
| `read -s` | Secret input | Masked input for passwords/tokens |
|
||||||
|
| `hermes config set` | Config YAML modification | Prefer over raw sed for individual key paths |
|
||||||
|
| `sed -i` | Config YAML fallback | For complex multi-line YAML blocks (e.g., docker_volumes array) |
|
||||||
|
| `crontab` | Cron job fallback | If `hermes cron create` is not available |
|
||||||
|
| `base64 -d` | Embedded file extraction | Embeds skill files and scripts as base64 in setup script |
|
||||||
|
|
||||||
|
## Package Legitimacy Audit
|
||||||
|
|
||||||
|
> No external packages from package registries (npm/PyPI/crates) are installed in this phase. All tools are installed via OS-level package managers (apt, curl binary downloads) or built into the Docker image. No `npm install`, `pip install`, or `cargo install` is needed.
|
||||||
|
|
||||||
|
| Package | Registry | Verdict | Disposition |
|
||||||
|
|---------|----------|---------|-------------|
|
||||||
|
| (none) | — | — | No packages to audit |
|
||||||
|
|
||||||
|
## Architecture Patterns
|
||||||
|
|
||||||
|
### System Architecture Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ ngn-agent Project Repo │
|
||||||
|
│ │
|
||||||
|
│ docker/ setup-ngn-agent.sh │
|
||||||
|
│ ├── Dockerfile (portable setup script) │
|
||||||
|
│ └── build.sh │
|
||||||
|
│ (builds image) │
|
||||||
|
└─────────┬───────────────────────────────────────┬───────────┘
|
||||||
|
│ │
|
||||||
|
▼ ▼
|
||||||
|
┌─────────────────────┐ ┌─────────────────────────────────┐
|
||||||
|
│ Custom Docker Image │ │ Fresh macOS Machine │
|
||||||
|
│ (tag: ngn-agent) │ │ │
|
||||||
|
│ │ │ ├─ Prerequisite checks │
|
||||||
|
│ Base: nikolaik/ │ │ │ (Hermes installed? │
|
||||||
|
│ python-nodejs │ │ │ Docker running? │
|
||||||
|
│ │ │ │ SSH keys exist?) │
|
||||||
|
│ Installed tools: │ │ ├─ Interactive secret prompts │
|
||||||
|
│ ├─ aws-cli 2.27.41 │ │ │ (JIRA_API_TOKEN, etc.) │
|
||||||
|
│ ├─ terraform 1.15.6│ │ ├─ Config file generation │
|
||||||
|
│ ├─ helm 4.2.1 │ │ │ (config.yaml, .env, etc.) │
|
||||||
|
│ ├─ kubectl 1.36.1 │ │ ├─ Script/skill copying │
|
||||||
|
│ └─ pup 1.1.0 │ │ ├─ Cron registration │
|
||||||
|
│ │ │ └─ Gateway restart offer │
|
||||||
|
└─────────────────────┘ └─────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
ngn-agent/
|
||||||
|
├── docker/
|
||||||
|
│ ├── Dockerfile # Custom Hermes image with added tools
|
||||||
|
│ └── build.sh # Single-command build script
|
||||||
|
├── setup-ngn-agent.sh # Portable setup script (standalone)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** The setup script embeds skill files and scripts as base64-encoded here-documents so it's fully self-contained. No external file dependencies needed.
|
||||||
|
|
||||||
|
### Pattern 1: Multi-Tool Dockerfile with Version Pinning
|
||||||
|
|
||||||
|
**What:** A Dockerfile that installs 5 platform engineering tools on top of the base Python+Node.js image, using version-pinned installations for reproducibility.
|
||||||
|
|
||||||
|
**When to use:** Any time a custom Hermes Docker image is built with additional CLI tools.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```dockerfile
|
||||||
|
# Source: [CITED: developer.hashicorp.com/terraform/install] + [CITED: docs.aws.amazon.com/cli/]
|
||||||
|
|
||||||
|
# Use ARGs for version pinning
|
||||||
|
ARG TERRAFORM_VERSION=1.15.6
|
||||||
|
ARG HELM_VERSION=4.2.1
|
||||||
|
ARG KUBECTL_VERSION=1.36.1
|
||||||
|
ARG PUPP_VERSION=1.1.0
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
unzip \
|
||||||
|
gnupg \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install AWS CLI v2 (no apt repo for v2 — use official installer)
|
||||||
|
RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
|
||||||
|
&& unzip -q awscliv2.zip \
|
||||||
|
&& ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli \
|
||||||
|
&& rm -rf awscliv2.zip aws/
|
||||||
|
|
||||||
|
# Install Terraform via HashiCorp apt repo
|
||||||
|
RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg \
|
||||||
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(grep -oP '(?<=UBUNTU_CODENAME=).*' /etc/os-release || lsb_release -cs) main" \
|
||||||
|
| tee /etc/apt/sources.list.d/hashicorp.list \
|
||||||
|
&& apt-get update && apt-get install -y terraform=${TERRAFORM_VERSION} \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install kubectl via Google Kubernetes apt repo
|
||||||
|
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg \
|
||||||
|
&& echo 'deb [signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' \
|
||||||
|
| tee /etc/apt/sources.list.d/kubernetes.list \
|
||||||
|
&& apt-get update && apt-get install -y kubectl=${KUBECTL_VERSION}-* \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Helm via Buildkite apt repo
|
||||||
|
RUN curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor -o /usr/share/keyrings/helm.gpg \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" \
|
||||||
|
| tee /etc/apt/sources.list.d/helm-stable-debian.list \
|
||||||
|
&& apt-get update && apt-get install -y helm=${HELM_VERSION} \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Datadog CLI (pup) — Rust binary from GitHub releases
|
||||||
|
RUN curl -fsSL "https://github.com/DataDog/pup/releases/download/v${PUPP_VERSION}/pup_${PUPP_VERSION}_Linux_x86_64.tar.gz" \
|
||||||
|
-o /tmp/pup.tar.gz \
|
||||||
|
&& tar xzf /tmp/pup.tar.gz -C /usr/local/bin/ pup \
|
||||||
|
&& rm -f /tmp/pup.tar.gz
|
||||||
|
|
||||||
|
# Verify all tools
|
||||||
|
RUN aws --version && terraform --version && helm version && kubectl version --client && pup --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 2: Interactive Secret Prompt with Validation
|
||||||
|
|
||||||
|
**What:** A bash function that prompts for a secret with masked input, validates it's non-empty, and offers to retry if empty.
|
||||||
|
|
||||||
|
**When to use:** Any bash setup script that needs to collect API tokens or passwords interactively.
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Source: Standard bash pattern — no single authoritative source
|
||||||
|
prompt_secret() {
|
||||||
|
local var_name="$1"
|
||||||
|
local prompt_text="$2"
|
||||||
|
local default="${3:-}"
|
||||||
|
local val=""
|
||||||
|
|
||||||
|
while [ -z "$val" ]; do
|
||||||
|
if [ -n "$default" ]; then
|
||||||
|
read -s -p "$prompt_text (default: ${default:0:4}...): " val
|
||||||
|
else
|
||||||
|
read -s -p "$prompt_text: " val
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
if [ -z "$val" ] && [ -n "$default" ]; then
|
||||||
|
val="$default"
|
||||||
|
break
|
||||||
|
elif [ -z "$val" ]; then
|
||||||
|
echo " ⚠ Value cannot be empty. Press Ctrl+C to cancel."
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
echo "$val"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Using `hermes config set` vs `sed` for YAML
|
||||||
|
|
||||||
|
**What:** The Hermes CLI provides `hermes config set <path> <value>` for individual key-value pairs in config.yaml. For complex structures (arrays like `docker_volumes`), fall back to `sed` or YAML-aware tools.
|
||||||
|
|
||||||
|
**When to use:** Prefer `hermes config set` for simple key paths. Use `sed` for multi-line YAML sections (e.g., the entire `terminal:` block with docker_volumes list).
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```bash
|
||||||
|
# Simple key-value — use hermes config set
|
||||||
|
hermes config set memory.provider hindsight
|
||||||
|
hermes config set terminal.backend docker
|
||||||
|
hermes config set terminal.timezone Asia/Singapore
|
||||||
|
hermes config set approvals.mode manual
|
||||||
|
|
||||||
|
# Complex YAML structures — use sed with a heredoc template
|
||||||
|
# Example: updating terminal.docker_image
|
||||||
|
hermes config set terminal.docker_image ngn-agent:latest
|
||||||
|
|
||||||
|
# docker_volumes is an array — build via sed or yq
|
||||||
|
# For arrays, hermes config set may not work; use sed or Python
|
||||||
|
```
|
||||||
|
|
||||||
|
### Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
- **Installing tools via `apt-get install` without version pinning** — leads to unreproducible builds. Always pin versions with `=version` syntax or download specific releases.
|
||||||
|
- **Using `snap` in Docker** — snap requires systemd which doesn't run inside Docker containers. Use curl/apt binary installation instead.
|
||||||
|
- **Hardcoding user paths in config templates** — the setup script must parameterize all paths (SSH keys, repo paths).
|
||||||
|
- **Overwriting existing config without backup** — setup script should back up existing `~/.hermes/config.yaml` before modifying.
|
||||||
|
|
||||||
|
## Don't Hand-Roll
|
||||||
|
|
||||||
|
| Problem | Don't Build | Use Instead | Why |
|
||||||
|
|---------|-------------|-------------|-----|
|
||||||
|
| YAML manipulation in bash | Custom YAML parser with `sed`/`awk` | `hermes config set` (for simple keys), `yq` or Python `yaml` for complex structures | YAML is whitespace-sensitive; fragile with sed |
|
||||||
|
| Cron job management | Writing to crontab directly | `hermes cron create` CLI | Hermes cron is managed in its internal DB, not system crontab; crontab entries would bypass Hermes delivery |
|
||||||
|
| SSH key generation | Automated SSH key creation | Skip — require user to have keys; script just validates they exist | SSH keys with passphrase prompting would break headless operation |
|
||||||
|
| AWS credential handling | Storing AWS keys in .env | Use existing `./.aws/` SSO config mounted as volume | ngn-agent uses SSO role chaining, not static keys |
|
||||||
|
| Docker image registry | Pushing to Docker Hub/GHCR | Tag as `ngn-agent:latest` local only | No CI/CD pipeline established; manual `docker build` in CONTEXT.md scope |
|
||||||
|
|
||||||
|
**Key insight:** Three things in this phase already have canonical solutions from Hermes itself (`hermes config set`, `hermes cron create`) or from the project's existing architecture (SSO-based AWS auth, SSH key assumption). Building alternatives to these is wasted effort.
|
||||||
|
|
||||||
|
## Common Pitfalls
|
||||||
|
|
||||||
|
### Pitfall 1: Base image tag `python3.11-nodejs20` is deprecated
|
||||||
|
**What goes wrong:** Docker build fails with `manifest for nikolaik/python-nodejs:python3.11-nodejs20 not found`.
|
||||||
|
**Why it happens:** The image maintainer drops tags when Node.js versions reach EOL. Node.js 20 reached EOL in April 2026.
|
||||||
|
**How to avoid:** Use `python3.11-nodejs22` or `python3.11-nodejs22-bookworm` instead — Node.js 22 is LTS until April 2027.
|
||||||
|
**Warning signs:** `docker pull nikolaik/python-nodejs:python3.11-nodejs20` fails with manifest error.
|
||||||
|
|
||||||
|
### Pitfall 2: `hermes cron create` requires specific CLI syntax
|
||||||
|
**What goes wrong:** Cron job creation fails with CLI errors.
|
||||||
|
**Why it happens:** The `hermes cron create` CLI has evolved across Hermes versions. The Phase 8 summaries documented the correct syntax: `hermes cron create --deliver telegram --skill session '0 9 * * *' 'prompt text'`.
|
||||||
|
**How to avoid:** Use the exact patterns verified in Phase 8:
|
||||||
|
- Skill-backed: `hermes cron create --deliver telegram --skill <name> 'schedule' 'prompt'`
|
||||||
|
- No-agent: `hermes cron create --no-agent --script <path> 'schedule'`
|
||||||
|
**Warning signs:** `Unknown flag` error from hermes CLI.
|
||||||
|
|
||||||
|
### Pitfall 3: `apt-get install terraform` version pinning format
|
||||||
|
**What goes wrong:** Apt fails to find the exact version specified.
|
||||||
|
**Why it happens:** HashiCorp's apt repo uses specific version formats. The installable version string for terraform is `1.15.6` (just the X.Y.Z).
|
||||||
|
**How to avoid:** Use `apt-get install -y terraform=1.15.6` — the version string matches the release tag.
|
||||||
|
**Warning signs:** `E: Version '1.15.6-1' not found` — try without the `-1` suffix.
|
||||||
|
|
||||||
|
### Pitfall 4: Embedded script files in setup script become stale
|
||||||
|
**What goes wrong:** The setup script copies skill files and scripts that are included as base64-encoded content, but these drift from the actual source files in `~/.hermes/`.
|
||||||
|
**Why it happens:** The setup script is a snapshot at the time of creation; the skill files evolve independently.
|
||||||
|
**How to avoid:** Source the skill files from the project repo at setup time rather than embedding them. If the skills live in git, copy from the cloned repo. If not, add a `--snapshot-date` comment in the script header noting when the embedded content was frozen.
|
||||||
|
**Warning signs:** User runs setup in 3 months and gets outdated skills.
|
||||||
|
|
||||||
|
### Pitfall 5: Dockerfile RUN layer cache busting
|
||||||
|
**What goes wrong:** Changing one tool's version rebuilds all subsequent layers because they're in separate RUN commands that invalidate the apt cache.
|
||||||
|
**Why it happens:** Each RUN command creates a new layer. If a tool's download URL changes, all subsequent layers after it are invalidated.
|
||||||
|
**How to avoid:** Order installs from most-frequently-changed (pup, kubectl) to least-changed (terraform, aws-cli), or combine all apt installs into one RUN.
|
||||||
|
|
||||||
|
## Code Examples
|
||||||
|
|
||||||
|
### Dockerfile Complete — Multi-Tool Installation
|
||||||
|
|
||||||
|
```dockerfile
|
||||||
|
# Source: [CITED: Multiple official tool installation docs — see Standard Stack]
|
||||||
|
ARG PYTHON_NODEJS_TAG=python3.11-nodejs22-bookworm
|
||||||
|
FROM nikolaik/python-nodejs:${PYTHON_NODEJS_TAG}
|
||||||
|
|
||||||
|
LABEL description="ngn-agent: Custom Hermes Docker image with platform engineering tools"
|
||||||
|
LABEL maintainer="ngn-agent"
|
||||||
|
|
||||||
|
# Tool versions — pin for reproducibility
|
||||||
|
ARG TERRAFORM_VERSION=1.15.6
|
||||||
|
ARG HELM_VERSION=4.2.1
|
||||||
|
ARG KUBECTL_VERSION=1.36.1
|
||||||
|
ARG PUPP_VERSION=1.1.0
|
||||||
|
|
||||||
|
# Install system dependencies
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl \
|
||||||
|
ca-certificates \
|
||||||
|
unzip \
|
||||||
|
gnupg \
|
||||||
|
wget \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install AWS CLI v2 (official installer — no apt repo for v2)
|
||||||
|
RUN curl -fsSL "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
|
||||||
|
&& unzip -q awscliv2.zip \
|
||||||
|
&& ./aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli \
|
||||||
|
&& rm -rf awscliv2.zip aws/
|
||||||
|
|
||||||
|
# Install Terraform (HashiCorp apt repo)
|
||||||
|
RUN wget -O- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg \
|
||||||
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \
|
||||||
|
| tee /etc/apt/sources.list.d/hashicorp.list \
|
||||||
|
&& apt-get update && apt-get install -y terraform=${TERRAFORM_VERSION} \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install kubectl (Google Kubernetes apt repo)
|
||||||
|
RUN curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key | gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg \
|
||||||
|
&& echo 'deb [signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' \
|
||||||
|
| tee /etc/apt/sources.list.d/kubernetes.list \
|
||||||
|
&& apt-get update && apt-get install -y kubectl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Helm (Buildkite apt repo)
|
||||||
|
RUN curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey | gpg --dearmor -o /usr/share/keyrings/helm.gpg \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" \
|
||||||
|
| tee /etc/apt/sources.list.d/helm-stable-debian.list \
|
||||||
|
&& apt-get update && apt-get install -y helm \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
# Install Datadog CLI (pup) — Rust binary from GitHub releases
|
||||||
|
RUN curl -fsSL "https://github.com/DataDog/pup/releases/download/v${PUPP_VERSION}/pup_${PUPP_VERSION}_Linux_x86_64.tar.gz" \
|
||||||
|
-o /tmp/pup.tar.gz \
|
||||||
|
&& tar xzf /tmp/pup.tar.gz -C /usr/local/bin/ pup \
|
||||||
|
&& rm -f /tmp/pup.tar.gz
|
||||||
|
|
||||||
|
# Verify all installations
|
||||||
|
RUN echo "=== Tool versions ===" \
|
||||||
|
&& aws --version \
|
||||||
|
&& terraform --version \
|
||||||
|
&& helm version --short \
|
||||||
|
&& kubectl version --client --output=yaml 2>/dev/null | grep gitVersion \
|
||||||
|
&& pup --version
|
||||||
|
|
||||||
|
# Default command (matching base image behavior)
|
||||||
|
CMD ["bash"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Script
|
||||||
|
|
||||||
|
```bash
|
||||||
|
#!/bin/bash
|
||||||
|
# Source: Project convention
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
IMAGE_NAME="ngn-agent"
|
||||||
|
IMAGE_TAG="latest"
|
||||||
|
DOCKER_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
echo "==> Building ${IMAGE_NAME}:${IMAGE_TAG}..."
|
||||||
|
|
||||||
|
docker build \
|
||||||
|
-t "${IMAGE_NAME}:${IMAGE_TAG}" \
|
||||||
|
-f "${DOCKER_DIR}/Dockerfile" \
|
||||||
|
"${DOCKER_DIR}"
|
||||||
|
|
||||||
|
echo "==> Build complete: ${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
docker images "${IMAGE_NAME}:${IMAGE_TAG}"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Setup Script — Heresd Config Template Generation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Source: Derived from current config.yaml state [VERIFIED: ~/.hermes/config.yaml]
|
||||||
|
generate_config_yaml() {
|
||||||
|
local ssh_key_1="$1"
|
||||||
|
local ssh_key_2="$2"
|
||||||
|
local ssh_config="$3"
|
||||||
|
local ssh_known_hosts="$4"
|
||||||
|
local repo_ops="$5"
|
||||||
|
local repo_deploy="$6"
|
||||||
|
local repo_devtools="$7"
|
||||||
|
local timezone="$8"
|
||||||
|
local docker_image="$9"
|
||||||
|
|
||||||
|
# Backup existing config
|
||||||
|
if [ -f ~/.hermes/config.yaml ]; then
|
||||||
|
cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak.$(date +%Y%m%d_%H%M%S)
|
||||||
|
echo " → Backed up existing config.yaml"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Use hermes config set for simple keys
|
||||||
|
hermes config set terminal.backend docker
|
||||||
|
hermes config set terminal.docker_image "${docker_image}"
|
||||||
|
hermes config set terminal.cwd /workspace
|
||||||
|
hermes config set terminal.container_memory 5120
|
||||||
|
hermes config set terminal.container_disk 51200
|
||||||
|
hermes config set terminal.container_cpu 1
|
||||||
|
hermes config set terminal.lifetime_seconds 300
|
||||||
|
hermes config set memory.provider hindsight
|
||||||
|
hermes config set timezone "${timezone}"
|
||||||
|
hermes config set telegram.reactions false
|
||||||
|
hermes config set terminal.docker_env.AWS_REGION us-east-1
|
||||||
|
|
||||||
|
# docker_volumes and shell_init_files need sed or Python for array manipulation
|
||||||
|
# Python is available on macOS — use it for safe YAML modification
|
||||||
|
python3 -c "
|
||||||
|
import yaml, sys
|
||||||
|
|
||||||
|
path = os.path.expanduser('~/.hermes/config.yaml')
|
||||||
|
with open(path) as f:
|
||||||
|
config = yaml.safe_load(f)
|
||||||
|
|
||||||
|
config['terminal']['docker_volumes'] = [
|
||||||
|
'${ssh_key_1}:/root/.ssh/id_ed25519razer:ro',
|
||||||
|
'${ssh_key_2}:/root/.ssh/id_rsa:ro',
|
||||||
|
'${ssh_config}:/root/.ssh/config:ro',
|
||||||
|
'${ssh_known_hosts}:/root/.ssh/known_hosts:ro',
|
||||||
|
'/Users/bapung/.aws/config:/root/.aws/config:ro',
|
||||||
|
'/Users/bapung/.aws/sso/cache:/root/.aws/sso/cache:rw',
|
||||||
|
'${repo_ops}:/workspace/rai-ops:rw',
|
||||||
|
'${repo_deploy}:/workspace/rai-deployment:rw',
|
||||||
|
'${repo_devtools}:/workspace/rai-devtools:rw',
|
||||||
|
os.path.expanduser('~/.hermes/scripts') + ':/usr/local/bin:ro',
|
||||||
|
]
|
||||||
|
|
||||||
|
config['terminal']['docker_forward_env'] = ['JIRA_EMAIL', 'JIRA_API_TOKEN', 'DEFAULT_REPOS']
|
||||||
|
config['terminal']['shell_init_files'] = ['/usr/local/bin/session-init.sh']
|
||||||
|
|
||||||
|
with open(path, 'w') as f:
|
||||||
|
yaml.dump(config, f, default_flow_style=False)
|
||||||
|
"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## State of the Art
|
||||||
|
|
||||||
|
| Old Approach | Current Approach | When Changed | Impact |
|
||||||
|
|--------------|------------------|--------------|--------|
|
||||||
|
| TFA (Terraform) pre-1.0 CLI syntax | Terraform 1.x stable CLI | 2020 | Current terraform 1.15.6 uses stable HCL syntax — no migration issues |
|
||||||
|
| AWS CLI v1 (Python pip package) | AWS CLI v2 (self-contained installer) | 2020 | v1 was `pip install awscli`; v2 uses curl→zip→install — Dockerfile must use official installer |
|
||||||
|
| Helm 2 (Tiller-based) | Helm 3/4 (client-only) | 2019/2025 | Helm 4.2.1 has no Tiller — simpler security model. APT repo install is same as Helm 3 |
|
||||||
|
| Pup CLI (pre-v1.0) | Pup 1.1.0 stable | 2026-06 | Pup now has stable release with OAuth2 auth; prebuilt binaries available |
|
||||||
|
|
||||||
|
**Deprecated/outdated:**
|
||||||
|
- `python3.11-nodejs20` base image tag: No longer published by maintainer. Use `python3.11-nodejs22-bookworm` instead.
|
||||||
|
- Snap packages in Docker: Snap requires systemd. Don't use `snap install aws-cli --classic` in Dockerfile.
|
||||||
|
|
||||||
|
## Assumptions Log
|
||||||
|
|
||||||
|
| # | Claim | Section | Risk if Wrong |
|
||||||
|
|---|-------|---------|---------------|
|
||||||
|
| A1 | The base image tag `python3.11-nodejs20` is no longer available on Docker Hub | Standard Stack | Build fails with manifest not found — must verify and update tag |
|
||||||
|
| A2 | Hermes v0.16+ `hermes config set` supports setting nested paths like `terminal.docker_image` | Don't Hand-Roll | May need to use Python/sed instead — minimal impact, fallback exists |
|
||||||
|
| A3 | Hermes v0.16+ `hermes cron create` matches Phase 8 documented syntax | Common Pitfalls | Cron job registration may fail with different syntax |
|
||||||
|
| A4 | All five skills can be embedded in the setup script | Architecture Patterns | Skills directory structure may have hidden files (metadata files) that aren't SKILL.md |
|
||||||
|
| A5 | `nikolaik/python-nodejs` base image has `python3` available for YAML manipulation | Code Examples | Would need to use `pip install pyyaml` in setup script or use `yq` instead |
|
||||||
|
|
||||||
|
## Open Questions (RESOLVED)
|
||||||
|
|
||||||
|
1. **Is `hermes config set` capable of setting YAML array values (like `docker_volumes`)?** — RESOLVED: Use `hermes config set` for scalar values only. For YAML arrays like `docker_volumes`, `shell_init_files`, and `docker_forward_env`, use Python's `yaml` module for manipulation with a `sed` fallback if Python is unavailable. The plan's setup script implements this hybrid approach.
|
||||||
|
|
||||||
|
2. **What is the correct kubectl apt package version string for version pinning?** — RESOLVED: Do NOT pin kubectl version — install `kubectl` without version specifier since it needs to match the target cluster version which may vary across environments. Other tools (terraform, helm, aws-cli, pup) are pinned via version ARGs.
|
||||||
|
|
||||||
|
3. **Should the setup script embed skill/script content as base64 or reference files from the git repo?** — RESOLVED: Embed skill and script content directly in the setup script using heredocs (with a snapshot date comment). This makes the script fully self-contained — no git repo dependency at setup time. When the script is regenerated for new versions, the embedded content is updated.
|
||||||
|
|
||||||
|
## Environment Availability
|
||||||
|
|
||||||
|
> Skip this section — the phase has no external dependencies that need runtime probing. Docker image build requires Docker (verified: `Docker version 29.4.0, build 9d7ad9f` — available). Setup script runs on macOS target with bash built-in, `hermes` CLI assumed present (v0.16+).
|
||||||
|
|
||||||
|
## Validation Architecture
|
||||||
|
|
||||||
|
> Skipped — `workflow.nyquist_validation` is explicitly `false` in `.planning/config.json`.
|
||||||
|
|
||||||
|
## Security Domain
|
||||||
|
|
||||||
|
> The `security_enforcement` key is absent from `.planning/config.json` (default: enabled).
|
||||||
|
|
||||||
|
### Applicable ASVS Categories
|
||||||
|
|
||||||
|
| ASVS Category | Applies | Standard Control |
|
||||||
|
|---------------|---------|-----------------|
|
||||||
|
| V5 Input Validation | yes | Setup script validates secret non-empty before accepting; validates SSH key paths exist |
|
||||||
|
| V7 Cryptography at Rest | partial | Secrets stored in `~/.hermes/.env` (plaintext file at rest). Acceptable for local machine — Hermes itself manages file permissions. |
|
||||||
|
| V9 Cryptographic Architecture | no | No custom crypto — tools use their own auth mechanisms |
|
||||||
|
|
||||||
|
### Known Threat Patterns for {Dockerfile + setup script}
|
||||||
|
|
||||||
|
| Pattern | STRIDE | Standard Mitigation |
|
||||||
|
|---------|--------|---------------------|
|
||||||
|
| Secret exposure in terminal history | Information Disclosure | Setup script uses `read -s` (masked input, no echo). Secrets are never echoed to terminal. |
|
||||||
|
| Config file world-readable permissions | Tampering | Script sets `chmod 600` on `~/.hermes/.env` after writing |
|
||||||
|
| Man-in-the-middle on tool download | Tampering | Dockerfile uses HTTPS for all downloads (AWS S3, GitHub, HashiCorp, pkgs.k8s.io, Buildkite). GPG signature verification in apt repos. |
|
||||||
|
| Accidental build context leak | Information Disclosure | Dockerfile should not `COPY .` — use `COPY docker/` only to avoid leaking `.env` or other secrets into image layers |
|
||||||
|
|
||||||
|
## Sources
|
||||||
|
|
||||||
|
### Primary (HIGH confidence)
|
||||||
|
- [AWS CLI v2 Linux install](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html) — verified official curl→unzip→install procedure
|
||||||
|
- [Terraform Linux install](https://developer.hashicorp.com/terraform/install#linux) — verified HashiCorp apt repo method + version 1.15.6
|
||||||
|
- [Helm install via apt](https://helm.sh/docs/intro/install/#from-apt-debianubuntu) — verified Buildkite apt repo method + version 4.2.1
|
||||||
|
- [kubectl Linux install](https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/) — verified Google Kubernetes apt repo method
|
||||||
|
- [Kubernetes releases](https://kubernetes.io/releases/) — verified latest stable v1.36.1
|
||||||
|
- [Pup CLI releases](https://github.com/DataDog/pup/releases/tag/v1.1.0) — verified latest version 1.1.0
|
||||||
|
- [nikolaik/python-nodejs Docker Hub](https://hub.docker.com/r/nikolaik/python-nodejs) — verified available tags and versions
|
||||||
|
- [Current ~/.hermes/config.yaml](file://~/.hermes/config.yaml) — verified 565-line source of truth for setup script
|
||||||
|
- [Current ~/.hermes/.env](file://~/.hermes/.env) — verified 484-line env template
|
||||||
|
- [Current ~/.hermes/hindsight/config.json](file://~/.hermes/hindsight/config.json) — verified JSON file content
|
||||||
|
- [Current session-init.sh](file://~/.hermes/scripts/session-init.sh) — verified 37-line script
|
||||||
|
- [Current archive-stale-sessions.sh](file://~/.hermes/scripts/archive-stale-sessions.sh) — verified 41-line script
|
||||||
|
- [Phase 8 cron registration patterns](file:///Users/bapung/Razer/ngn-agent/.planning/phases/08-cron-reporting/08-01-SUMMARY.md) — verified `hermes cron create` CLI syntax
|
||||||
|
|
||||||
|
### Secondary (MEDIUM confidence)
|
||||||
|
- [Hermes research: Extensibility](file:///Users/bapung/Razer/ngn-agent/.planning/research/hermes/EXTENSIBILITY.md) — verified hermes config set capability
|
||||||
|
- [Base image GitHub repo](https://github.com/nikolaik/docker-python-nodejs) — confirmed tag generation pattern
|
||||||
|
|
||||||
|
### Tertiary (LOW confidence)
|
||||||
|
- (none — all claims verified via official sources or current config files)
|
||||||
|
|
||||||
|
## Metadata
|
||||||
|
|
||||||
|
**Confidence breakdown:**
|
||||||
|
- Standard stack: HIGH — all tool versions verified from official sources
|
||||||
|
- Architecture: HIGH — patterns derived from current working configuration
|
||||||
|
- Pitfalls: HIGH — based on documented deprecations and Phase 8 execution knowledge
|
||||||
|
- Base image tag availability: MEDIUM — need to confirm `python3.11-nodejs20` tag status at build time
|
||||||
|
|
||||||
|
**Research date:** 2026-06-15
|
||||||
|
**Valid until:** 2026-07-15 (tool versions may receive patch updates within 30 days; base image tags may change if maintainer drops more versions)
|
||||||
53
docker/Dockerfile
Normal file
53
docker/Dockerfile
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
FROM nikolaik/python-nodejs:python3.11-nodejs20 AS base
|
||||||
|
|
||||||
|
LABEL description="ngn-agent: Custom Hermes Docker image with platform engineering tools"
|
||||||
|
LABEL maintainer="ngn-agent"
|
||||||
|
|
||||||
|
ARG TERRAFORM_VERSION=1.15.6
|
||||||
|
ARG HELM_VERSION=4.2.0
|
||||||
|
ARG KUBECTL_VERSION=1.36.1
|
||||||
|
ARG PUPP_VERSION=1.1.0
|
||||||
|
|
||||||
|
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
|
||||||
|
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
curl ca-certificates unzip gnupg wget \
|
||||||
|
&& wget -O- https://apt.releases.hashicorp.com/gpg 2>/dev/null \
|
||||||
|
| gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg \
|
||||||
|
&& curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.36/deb/Release.key \
|
||||||
|
| gpg --dearmor -o /usr/share/keyrings/kubernetes-apt-keyring.gpg \
|
||||||
|
&& curl -fsSL https://packages.buildkite.com/helm-linux/helm-debian/gpgkey \
|
||||||
|
| gpg --dearmor -o /usr/share/keyrings/helm.gpg \
|
||||||
|
&& . /etc/os-release \
|
||||||
|
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com ${VERSION_CODENAME} main" \
|
||||||
|
| tee /etc/apt/sources.list.d/hashicorp.list > /dev/null \
|
||||||
|
&& echo 'deb [signed-by=/usr/share/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.36/deb/ /' \
|
||||||
|
| tee /etc/apt/sources.list.d/kubernetes.list > /dev/null \
|
||||||
|
&& echo "deb [signed-by=/usr/share/keyrings/helm.gpg] https://packages.buildkite.com/helm-linux/helm-debian/any/ any main" \
|
||||||
|
| tee /etc/apt/sources.list.d/helm-stable-debian.list > /dev/null \
|
||||||
|
&& apt-get update && apt-get install -y --no-install-recommends \
|
||||||
|
terraform=${TERRAFORM_VERSION}-1 \
|
||||||
|
kubectl \
|
||||||
|
helm=${HELM_VERSION}-1 \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN ARCH=$(uname -m) && \
|
||||||
|
case "$ARCH" in \
|
||||||
|
x86_64) AWS_URL="https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip"; PUPP_ARCH="x86_64" ;; \
|
||||||
|
aarch64) AWS_URL="https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip"; PUPP_ARCH="arm64" ;; \
|
||||||
|
*) echo "Unsupported architecture: $ARCH" >&2; exit 1 ;; \
|
||||||
|
esac \
|
||||||
|
&& curl -fsSL "$AWS_URL" -o /tmp/awscliv2.zip \
|
||||||
|
&& unzip -q /tmp/awscliv2.zip -d /tmp \
|
||||||
|
&& /tmp/aws/install --bin-dir /usr/local/bin --install-dir /usr/local/aws-cli \
|
||||||
|
&& curl -fsSL "https://github.com/DataDog/pup/releases/download/v${PUPP_VERSION}/pup_${PUPP_VERSION}_Linux_${PUPP_ARCH}.tar.gz" -o /tmp/pup.tar.gz \
|
||||||
|
&& tar xzf /tmp/pup.tar.gz -C /usr/local/bin/ pup \
|
||||||
|
&& rm -rf /tmp/awscliv2.zip /tmp/aws /tmp/pup.tar.gz \
|
||||||
|
&& echo "=== Tool versions ===" \
|
||||||
|
&& aws --version \
|
||||||
|
&& terraform --version \
|
||||||
|
&& helm version --short \
|
||||||
|
&& kubectl version --client --output=yaml 2>/dev/null | grep gitVersion || true \
|
||||||
|
&& pup --version || true
|
||||||
|
|
||||||
|
CMD ["bash"]
|
||||||
22
docker/build.sh
Executable file
22
docker/build.sh
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
REGISTRY="${REGISTRY:-gitea.bpg.pw/bapung/ngn-agent}"
|
||||||
|
BUILD_TAG="${BUILD_TAG:-latest}"
|
||||||
|
|
||||||
|
DOCKER_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
echo "==> Building ${REGISTRY}:${BUILD_TAG}..."
|
||||||
|
docker build \
|
||||||
|
-t "${REGISTRY}:${BUILD_TAG}" \
|
||||||
|
-f "${DOCKER_DIR}/Dockerfile" \
|
||||||
|
"${DOCKER_DIR}"
|
||||||
|
|
||||||
|
echo "==> Build complete: ${REGISTRY}:${BUILD_TAG}"
|
||||||
|
docker images "${REGISTRY}:${BUILD_TAG}"
|
||||||
|
|
||||||
|
if [[ "${1:-}" == "--push" ]]; then
|
||||||
|
echo "==> Pushing to ${REGISTRY}:${BUILD_TAG}..."
|
||||||
|
docker push "${REGISTRY}:${BUILD_TAG}"
|
||||||
|
echo "==> Push complete"
|
||||||
|
fi
|
||||||
1374
ngn-agent/setup-ngn-agent.sh
Executable file
1374
ngn-agent/setup-ngn-agent.sh
Executable file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user