# Session Log

> Structured decision records for session logs and merge logs at phase boundaries

- Skill: `jankneumann/session-log` (Agent Skill, multi-file: 11 files)
- Install (CLI): `npx skillmds@latest add jankneumann/session-log`
- Raw SKILL.md: https://api.skillmd.com/api/skills/jankneumann/session-log/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: jankneumann (https://skillmd.com/u/jankneumann)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/jankneumann/session-log

---


# Session Log — Infrastructure Skill

Provides utilities for appending structured decision records to `session-log.md` (per-change) and `docs/merge-logs/YYYY-MM-DD.md` (per-merge-session). Each workflow skill calls these at phase boundaries to build a living decision record.

## Scripts

### `scripts/extract_session_log.py`

Append-based utilities for session log and merge log management.

**Functions:**

- `append_phase_entry(change_id, phase_name, content, session_log_path=None)` — Append a phase entry to `session-log.md`. Creates the file with header if missing. Returns the Path.
- `append_merge_entry(date, content, merge_log_path=None)` — Append a merge session entry to a dated merge-log file. Creates the file with header if missing. Returns the Path.
- `count_phase_iterations(phase_prefix, session_log_path)` — Count existing iteration entries for auto-incrementing `<N>` in phase names like "Plan Iteration 2".
- `generate_self_summary_prompt(change_id)` — Reference template for structuring phase entries (optional utility).

**Usage from Python:**

```python
from extract_session_log import append_phase_entry, count_phase_iterations

n = count_phase_iterations("Plan Iteration", "openspec/changes/my-change/session-log.md") + 1
append_phase_entry("my-change", f"Plan Iteration {n}", content)
```

**Usage from SKILL.md (agent writes directly):**

Skills embed the phase entry template directly. The agent writes the markdown content, then the skill runs sanitization. The Python functions are optional consistency helpers.

### `scripts/sanitize_session_log.py`

Detects and redacts secrets, high-entropy strings, and environment-specific paths from session log content before it is committed to git. Supports in-place operation (same path for input and output).

```bash
# In-place sanitization (recommended)
python3 "<skill-base-dir>/scripts/sanitize_session_log.py" session-log.md session-log.md

# Separate output
python3 "<skill-base-dir>/scripts/sanitize_session_log.py" input.md output.md

# Dry run
python3 "<skill-base-dir>/scripts/sanitize_session_log.py" input.md output.md --dry-run
```

**What gets redacted**: AWS keys, GitHub tokens, Anthropic/OpenAI keys, connection strings, private key headers, password fields, high-entropy strings (>4.5 bits/char, >20 chars).

**What is preserved**: Git SHAs, UUIDs, OpenSpec change-ids, file paths (normalized), kebab-case identifiers.

**Exit codes**: 0 = success, 1 = sanitization error (do NOT commit the output).

### `scripts/phase_record.py` (preferred API)

Unified data model that renders to both session-log markdown AND coordinator handoff JSON from a single in-memory object. Use this in place of `append_phase_entry` for new code.

**Dataclasses**: `PhaseRecord`, `Decision`, `Alternative`, `TradeOff`, `CapabilityGap`, `FileRef`, `PhaseWriteResult`.

**Usage from Python (skill entry-point)**:

```python
from phase_record import PhaseRecord, Decision, FileRef

record = PhaseRecord(
    change_id="my-change",
    phase_name="Plan",
    agent_type="claude_code",
    summary="Selected approach A; deferred X.",
    decisions=[
        Decision(title="Use approach A", rationale="...", capability="skill-workflow"),
    ],
    relevant_files=[FileRef(path="src/foo.py", description="entrypoint")],
)

result = record.write_both()
# result.markdown_path → Path to session-log.md
# result.handoff_id    → coordinator handoff_id (or None on fallback)
# result.handoff_local_path → openspec/changes/<id>/handoffs/<phase>-<N>.json on fallback
# result.warnings      → list of soft-failure messages (each step is best-effort)
```

**Persistence pipeline** (3 best-effort steps; failures log warnings but never raise):
1. **append markdown** → `openspec/changes/<change-id>/session-log.md`
2. **sanitize in-place** → runs `sanitize_session_log.py` on the file
3. **coordinator write** → `try_handoff_write` via `coordination_bridge`, OR fallback to `openspec/changes/<change-id>/handoffs/<phase-slug>-<N>.json`

**Round-trip APIs**:
- `record.render_markdown()` → markdown (preserves `architectural:` and `supersedes:` inline spans)
- `parse_markdown(text, change_id=...)` → reconstructs PhaseRecord
- `record.to_handoff_payload()` → dict matching `HandoffService.write` arguments
- `PhaseRecord.from_handoff_payload(d, change_id=..., phase_name=...)` → reconstructs

**Why prefer this over `append_phase_entry`**: `append_phase_entry` is now a deprecation-warned shim that internally constructs a minimal PhaseRecord and calls `write_both()`. Using `PhaseRecord(...).write_both()` directly gives you structured fields (Decisions, Alternatives, Trade-offs, Completed Work, etc.) instead of free-form markdown content, which the coordinator can consume via `read_handoff` and the next phase can hydrate via `PhaseRecord.from_handoff_payload`.

## Phase Entry Template

Each workflow phase appends a section following this structure:

```markdown
---

## Phase: <phase-name> (<YYYY-MM-DD>)

**Agent**: <agent-type> | **Session**: <session-id-or-N/A>

### Decisions
1. **<Decision title>** `architectural: <capability>` — <rationale>
2. **<Routine decision title>** — <rationale>

### Alternatives Considered
- <Alternative>: rejected because <reason>

### Trade-offs
- Accepted <X> over <Y> because <reason>

### Capability Gaps Observed
- **<failure_type>**: <capability_gap description> (skill: <affected_skill>, severity: <low|medium|high|critical>)

### Open Questions
- [ ] <unresolved question>

### Completed Work
- <concrete deliverable landed in this phase>

### In Progress
- <work started but not finished>

### Next Steps
- <what the next phase should pick up>

### Relevant Files
- `<path/to/file.py>` — <short description of why this file matters>

### Context
<2-3 sentences: what was the goal, what happened>
```

**Section names must be identical across all skills**: Decisions, Alternatives Considered, Trade-offs, Capability Gaps Observed, Open Questions, Completed Work, In Progress, Next Steps, Relevant Files, Context.

The five added sections (Capability Gaps Observed, Completed Work, In Progress, Next Steps, Relevant Files) are **optional** — omit them when empty. They mirror the structured `PhaseRecord` fields written to the coordinator handoff (see `<skill-base-dir>/scripts/phase_record.py`), so a skill using `PhaseRecord(...).write_both()` produces matching content in both `session-log.md` and `handoff_documents`.

**Capability Gaps Observed** captures gaps the agent noticed during the phase. Each entry uses the D4 shared tag schema (`failure_type`, `capability_gap`, `affected_skill`, `severity`). On `write_both()`, each gap is also emitted to episodic memory with `source:session-log`. See `docs/guides/memory-conventions.md` for the full tag schema.

**When no decisions were made** (e.g., validation passed cleanly): include Context, write "No significant decisions required" in Decisions, omit other sections.

### Tagging decisions for the per-capability index

Decisions that shape how a capability behaves across multiple changes SHOULD carry an inline ``` `architectural: <capability>` ``` tag so they are picked up by the per-capability decision index (`docs/decisions/<capability>.md`, regenerated by `make decisions`).

**Syntax** — a backtick-delimited inline code span placed after the title and before the `—` delimiter:

```markdown
1. **Pin worktrees during overnight pauses** `architectural: software-factory-tooling` — prevents GC during idle
2. **Use `--` separator for parallel agent branches** `architectural: software-factory-tooling` — `/` would collide with parent feature branch ref
3. **Extend Phase Entry with archetype hints** `architectural: skill-workflow` — needed for archetype routing
```

`<capability>` MUST be a kebab-case identifier (lowercase letters, digits, hyphens; 2–52 chars) that matches a directory name under `openspec/specs/`. Invalid capabilities (unknown name, bad format) are skipped in non-strict mode and fail CI in `--strict` mode.

**When to tag** — decisions that will be referenced, extended, or reversed by later work. Think: *"would someone doing archaeology on this capability six months from now care why we chose this?"*

**When NOT to tag** — routine engineering choices that do not outlive the current change (library selection within a dominant pattern, choice of internal helper name, etc.). Untagged decisions remain valid session-log entries — they just do not appear in the per-capability index.

**Supersession** — if a later decision reverses an earlier one, add a second inline span ``` `supersedes: <earlier-change-id>#D<n>` ```:

```markdown
1. **Replace Beads with built-in tracker** `architectural: agent-coordinator` `supersedes: 2026-02-xx-add-beads-integration#D1` — reduces vendor surface
```

The decision index then renders bidirectional **Supersedes** / **Superseded by** links between the two entries. `#D<n>` is the decision's 1-indexed position within its phase entry (e.g., `#D1` for the first bullet in Decisions).

If the target change has multiple phases that both carry a D<n> at the same bullet position (e.g., `Plan` D1 AND `Implementation` D1), use the phased form ``` `supersedes: <change-id>#<phase-slug>/D<n>` ``` — e.g., `` `supersedes: 2026-02-10-example#plan/D1` `` — to disambiguate. The phase slug is the phase name lowercased with spaces replaced by hyphens. The bare form is accepted when unambiguous (target change has only one phase at that bullet index); when ambiguous, the emitter emits a warning and skips the link rather than silently marking every matching phase as superseded.

## Append-Sanitize-Verify Flow

Every skill follows this 3-step pattern:

1. **APPEND**: Agent writes phase entry to session-log.md (or merge-log)
2. **SANITIZE**: Run `sanitize_session_log.py` on the file (in-place)
3. **VERIFY**: Agent reads sanitized output and checks:
   - All phase entry sections are present (or intentionally omitted)
   - No `[REDACTED:*]` markers in prose where original had no secrets
   - Markdown structure is intact
   - If over-redacted: rewrite without the triggering pattern, re-sanitize (one attempt max)
   - If sanitization exits non-zero: do NOT commit, log warning, continue workflow

```bash
python3 "<skill-base-dir>/../session-log/scripts/sanitize_session_log.py" \
  "openspec/changes/<change-id>/session-log.md" \
  "openspec/changes/<change-id>/session-log.md"
```

## Phase Names

| Skill | Phase Name |
|-------|-----------|
| plan-feature | `Plan` |
| iterate-on-plan | `Plan Iteration <N>` |
| implement-feature | `Implementation` |
| iterate-on-implementation | `Implementation Iteration <N>` |
| validate-feature | `Validation` |
| cleanup-feature | `Cleanup` |
| merge-pull-requests | (uses merge-log, not session-log) |

## Integration

This skill is called by workflow skills at phase boundaries:
- **Skills that commit**: plan-feature, implement-feature, cleanup-feature, iterate-on-plan, iterate-on-implementation — include session-log.md in existing commit
- **Skills that need a dedicated commit**: validate-feature — commit session-log.md separately
- **Merge log**: merge-pull-requests writes to `docs/merge-logs/YYYY-MM-DD.md`

## Tests

```bash
uv run --project "<skills-root>" pytest "<skill-base-dir>/scripts/" -v
```

