Skip to main content
Glama
mattmaas
by mattmaas
README.md
# Agent Sessions MCP - Session Context & Memory Bridge

Session lifecycle tracking, persistent context notes, transcript logging, and an
explicit, unverified memory-promotion pipeline for AI agents in your agent harness.

Permissions and tool access are handled by your host AI agent harness. This MCP
server provides session state tracking and the bridge between session context and
long-term memory.

## Session Types

| Type | Phases | Description |
|------|--------|-------------|
| **ask** | active | Read-only analysis and research |
| **plan** | active | Read-only, outputs actionable plan |
| **build** | active | Edit, run, verify. Use git for versioning |
| **architect** | planning → building → completed | Plan then Build with phase tracking |

## Lifecycle Model

Every session has a top-level **status**: `active` (default), `completed`, `blocked`,
or `cancelled`. The last three are **terminal**. `phase` remains the mode-workflow
field (e.g. architect planning/building/completed) and is separate from status.

- **Close** (`close_session`) sets a terminal status plus `closed_at`/`close_reason`.
  It is idempotent for an identical close and rejects a conflicting close.
- **Archive** (`archive_session`) is an independent, reversible flag
  (`archived`/`archived_at`/`archive_reason`). Archived sessions are hidden from
  default listings and reject writes.
- **Terminal/archived sessions are read-only**: `append_context_note`,
  `append_transcript`, `set_mode`, `architect_advance_to_build`, and
  `update_session_metadata` are rejected. Reads (`get_session`, `get_context`,
  `get_transcript`), `resume_session` (read-only with a warning), and
  promotion/export remain allowed.
- **Old state files are lazily normalized on read** (missing `status` ⇒ `active`,
  missing `archived` ⇒ `false`). Legacy architect records with `phase=completed`
  and no `status` normalize to `status=completed`, with `closed_at` derived from
  the existing `updated_at`/`created_at` and an explanatory `normalized: …`
  `close_reason`. Nothing is eagerly rewritten on disk.
- Every externally supplied session ID is validated against
  `^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$` before any path is constructed from it;
  directory scans skip invalid names instead of aborting.
- Every lifecycle/archive/metadata/stale mutation appends one auditable `system`
  event to `transcript.jsonl`. History is never rewritten, and **no hard delete is
  exposed anywhere**.
- `architect_complete` requires an architect session in `building` phase
  (idempotent for already-completed architects), rejects archived and terminal
  `blocked`/`cancelled` sessions, sets `phase=completed` **and**
  `status=completed`, appends one `architect_completed` event, and **never
  auto-promotes** to memory.

## Structured Linkage Metadata

`create_session` validates/normalizes these optional `metadata` fields exactly
(additional keys are preserved; `update_session_metadata` patches them later with
reserved-field validation):

| Field | Type | Default |
|---|---|---|
| `task_ids` | string[] | `[]` (references only — no Task Manager I/O) |
| `parent_session_id` | string \| null | `null` |
| `owner` | string \| null | `null` |
| `next_action` | string \| null | `null` |
| `verification_status` | `verified` \| `partial` \| `unverified` | `unverified` |
| `source_systems_checked` | string[] | `[]` |
| `tags` | string[] | `[]` |

## Run

```bash
npm start
```

Registered in your MCP client config as the `agent-sessions` MCP server.

## Tools (22)

### Session Management
- `list_modes` - Show all session types
- `create_session` - Start a new session (validated metadata, see above)
- `get_session` - Get session state
- `list_sessions` - List/filter sessions (newest updated first)
- `resume_session` - Resume the newest active non-archived session, or a specific one (closed/archived resume read-only)
- `set_mode` - Change session type (rejected when closed/archived)

### Lifecycle
- `close_session` - Close with terminal status `completed|blocked|cancelled` (idempotent/conflict-safe)
- `archive_session` - Archive/unarchive (reversible, idempotent)
- `update_session_metadata` - Patch metadata (reserved fields rejected; rejected when closed/archived)
- `report_stale_sessions` - Stale report: active, non-archived sessions, oldest-first by `updated_at`
- `cleanup_stale_sessions` - Close stale sessions (`blocked|cancelled`) then archive them. **Dry-run by default** (writes nothing); applying requires `dry_run=false` + `confirm=true`. Re-reads each candidate and skips it if status/archive/`updated_at` changed or it is no longer past the cutoff. Never deletes anything.

### Architect Phase Control
- `architect_advance_to_build` - Move from planning to building phase (rejected when closed/archived)
- `architect_complete` - Complete an architect session (closes it; never promotes)

### Context Persistence
- `append_transcript` - Log a turn to the session transcript (rejected when closed/archived)
- `get_transcript` - Retrieve transcript entries
- `append_context_note` - Add note to rolling context summary (rejected when closed/archived)
- `get_context` - Read the full context document

### Memory Promotion (explicit, unverified)
- `promote_session_memory` - Create a versioned (`schema_version: 1`), **unverified** promotion candidate in the queue. Deterministic content hash/candidate id; idempotent for identical content; changed content gets a hash-suffixed file (never an overwrite). Does **not** run the consumer.

### Promotion Consumer (safe wiki ingest; dry-run by default)
These tools always use the canonical current base root (`<OPENCODE_BASE_DIR>/.opencode`)
— no caller-supplied filesystem override is accepted. Explicit `baseDir` remains
available only inside the consumer module, its CLI, and tests. Quarantine entries
store safe metadata only (IDs, hashes, memory count, queue path, rule IDs/counts/
indexes) — never candidate memory contents.

- `promotion_status` - Read-only pipeline status (queue/inbox/quarantine/pages/ledger)
- `promotion_process` - Process queue candidates to inbox (clean) or quarantine. Dry-run unless `write=true`. Never modifies queue/session files.
- `promotion_lint` - Lint inbox entries (schema, hash, secret/PII scan). Read-only.
- `promotion_review` - Approve (writes a trusted wiki page only after lint+scan+provenance pass) or reject. **Requires `confirm=true` and a `reviewer`**; dry-run unless `write=true`.

No code path auto-trusts a candidate: queue → unverified inbox/quarantine → explicit
human review → trusted page (ledgered).

## list_sessions Filters

All filters are additive. Defaults: `lifecycle=all` (active + closed),
`include_archived=false`, `offset=0`, `limit=20`, order by `updated_at` descending.

- `workspace_root` - case-insensitive, normalized exact path match
- `mode` / `phase` / `status` - single value or array
- `lifecycle` - `active` | `closed` | `all`
- `created_after` / `created_before` / `updated_after` / `updated_before` - ISO dates (validated; inclusive `>=` / `<=`)
- `metadata` - subset match against session metadata
- `task_id` - matches `metadata.task_ids` entries and legacy `metadata.task_id`
- `parent_session_id` - exact match
- `tags` + `tag_match` - `all` (default) | `any`
- `keyword` - case-insensitive substring over `context.md` notes
- `offset` / `limit` / `include_state` - pagination and full-state embedding

Summaries include `status`, `closed_at`, `archived`, `workspace_root`, and the
linkage fields `task_ids`, `parent_session_id`, `tags`.

## Data Layout (canonical root)

State lives under the canonical root `<base>/.opencode/`, where `<base>` is
`OPENCODE_BASE_DIR` (default: `~/.config/opencode`):

```
.opencode/
  sessions/<session-id>/
    state.json          # mode, phase, status, archive flags, plan steps, metadata
    transcript.jsonl    # Append-only turn + system-event log
    context.md          # Rolling distilled context
  memory-promotions/<candidate-id>.json
                        # Unverified promotion candidates (schema_version 1)
  wiki/
    inbox/              # Processed, still-unverified candidates
    quarantine/         # Failed validation/scan/provenance (safe metadata only, no contents)
    pages/              # Trusted pages (explicit human approval only)
    review-ledger.jsonl # Append-only review events
```

**Restart note:** the sessions root is resolved from `OPENCODE_BASE_DIR` when the
server process starts. After migrating state or changing the env var, restart the
`agent-sessions` server (restart your agent harness) so it picks up the new root.

## Typical Flows

### Build Session
```
create_session(mode="build", workspace_root="C:/path/to/project")
-> edit files, run tests (use git for versioning via /undo or git MCP tools)
-> append_context_note for important decisions
-> close_session(status="completed") when done
-> promote_session_memory to queue an unverified candidate (optional)
```

### Architect Session (Plan + Build)
```
create_session(mode="architect", workspace_root="C:/path/to/project")
# Phase 1: Planning
-> analyze, produce plan
-> architect_advance_to_build(plan_steps=[...])
# Phase 2: Building
-> implement plan steps, commit via git
-> architect_complete()  # sets phase=completed AND status=completed; no auto-promotion
```

### Resume After Restart
```
resume_session()                    # resumes newest active non-archived session
resume_session(session_id="...")    # closed/archived sessions resume read-only
```

### Promotion Pipeline (human-gated)
```
promote_session_memory(session_id)          # queue an UNVERIFIED candidate
promotion_process()                         # dry-run triage (inbox vs quarantine)
promotion_process(write=true)               # write entries
promotion_lint()                            # re-validate inbox
promotion_review(candidate_id, action="approve",
                   confirm=true, reviewer="Matt Maas", write=true)
                                            # trusted wiki page + ledger entry
```

### Stale Hygiene (safe by default)
```
report_stale_sessions(stale_after_days=14)          # read-only report
cleanup_stale_sessions(stale_after_days=14)         # dry-run: writes nothing
cleanup_stale_sessions(stale_after_days=14,
                       dry_run=false, confirm=true,
                       close_status="blocked")      # close + archive, never delete
```

## Tests

```bash
node --test agent-sessions-lifecycle.test.mjs promotion-consumer.test.mjs
node test-migrate-state-root.mjs
node test-logging-gate.mjs
node test-integration.mjs   # tools/list smoke
```

All tests use temp roots (`OPENCODE_BASE_DIR` overrides) only; live session/queue/wiki
state is never touched.

## Versioning

File versioning is handled through git-based `/undo` `/redo` commands
and the git MCP server.