Skip to main content

state-persistence

Provides patterns for reading, writing, and recovering AgLoop state from state.json and plan.yaml. Includes checkpoint management and corruption recovery. Use when implementing state management logic or hook scripts.

Source facts

Repository
zebbern/termstack
Last source activity
April 10, 2026 at 22:53
Detected SKILL.md language
English
Stars
4
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
state-persistence
description
Provides patterns for reading, writing, and recovering AgLoop state from state.json and plan.yaml. Includes checkpoint management and corruption recovery. Use when implementing state management logic or hook scripts.
disable-model-invocation
true
argument-hint
State operation to perform (read, write, checkpoint, recover)
tags
["agloop","state","json","checkpoint","recovery","persistence"]
# State Persistence ## When to Use This Skill Load this skill when: - Reading or writing `.agloop/state.json` (any agent, hook scripts) - Implementing hook scripts that interact with state files - Creating or restoring checkpoints (pre-compact hook, session-start hook) - Recovering from corrupted or missing state - Appending entries to `.agloop/log.json` - Reading or validating `.agloop/plan.yaml` ## state.json Schema Reference The single source of truth for the agentic loop's runtime state. Every agent reads it before acting. Only the coordinator mutates it. ### Field Reference | Field | Type | Required | Default | Constraints | | ----------------------------- | ------------ | -------- | ------------------- | ----------------------------------------------------------------------- | | `$schema` | string | yes | `"agloop-state-v1"` | Literal value | | `feature_name` | string | yes | `""` | Non-empty after init, max 500 chars | | `feature_description` | string | no | `""` | Set after research phase | | `complexity_level` | enum | yes | `"medium"` | `simple \| medium \| complex`. Set during init based on feature scope | | `current_phase` | enum | yes | `"init"` | `init \| research \| plan \| critique \| execute \| verify \| complete` | | `iteration` | number | yes | `0` | 0 ≤ n ≤ 3 | | `current_task_id` | string\|null | yes | `null` | Must reference existing task or null | | `tasks` | array | yes | `[]` | Populated after plan phase | | `tasks[].id` | string | yes | — | Format: `task-NNN` | | `tasks[].title` | string | yes | — | Deliverable-focused | | `tasks[].description` | string | yes | — | Detailed requirements | | `tasks[].status` | enum | yes | `"pending"` | `pending \| in_progress \| done \| failed` | | `tasks[].depends_on` | string[] | yes | `[]` | All referenced IDs must exist | | `tasks[].acceptance_criteria` | string[] | yes | — | ≥1 criterion per task | | `tasks[].failure_modes` | object[] | yes | `[]` | Each: mode, likelihood, impact, mitigation | | `tasks[].result` | object\|null | yes | `null` | Set by executor | | `tasks[].verification` | object\|null | yes | `null` | Set by verifier | | `stop_hook_active` | boolean | yes | `false` | Prevents infinite stop-hook loops | | `last_action` | object | yes | — | Updated on every agent action | | `last_action.agent` | string | yes | — | Which agent performed the action | | `last_action.action` | string | yes | — | What was done | | `last_action.timestamp` | string | yes | — | ISO 8601 | | `last_action.task_id` | string\|null | yes | — | Related task or null | | `phase_history` | array | yes | `[]` | Append-only phase transitions | | `retry_count` | object | yes | `{}` | Keys: task IDs, values: integers ≥ 0 | | `previous_objections` | array | yes | `[]` | Critic objections from plan↔critique loop. Each: `{ iteration, objection, response, resolved, objection_category, severity }`. Reset on new plan phase | | `compaction_context` | object | yes | — | Recovery context for compaction survival. Sub-fields: `last_delegation`, `last_result_summary`, `research_digest`, `plan_digest`, `critique_digest`, `pending_decision`, `compaction_count` | ### Task Result Schema Set by the executor after task completion: ```json { "files_modified": ["src/api/search.ts", "src/api/search.test.ts"], "files_deleted": [], "acceptance_criteria_confirmation": [ { "criterion": "GET /api/search returns 200 with results array", "met": true, "evidence": "Handler returns res.json({ data: results, meta: { total, page }, error: null })" } ], "summary": "Implemented search endpoint with pagination and filtering", "warnings": [], "verified": false } ``` ### Task Verification Schema Set by the verifier after task verification: ```json { "verdict": "PASS", "criteria_results": [ { "criterion": "GET /api/search returns 200 with results array", "result": "pass", "evidence": "Response: { results: [...], total: 42, page: 1 }" } ], "specification_compliance": { "required_patterns": { "compliant": true, "violations": [] }, "forbidden_patterns": { "compliant": true, "violations": [] }, "tech_choices": { "compliant": true, "violations": [] } }, "overall_summary": "All acceptance criteria met, specification compliant" } ``` On FAIL, the verifier adds a `failure_details` field with specific failure information. ``` ## Read-Modify-Write Pattern **Every state mutation must follow this exact pattern.** Never read and write without validation. ### Step-by-Step Procedure ```javascript // 1. READ — Load current state from disk const raw = await fs.readFile(".agloop/state.json", "utf-8"); // 2. PARSE — Convert to object with error handling let state; try { state = JSON.parse(raw); } catch (err) { // State corrupted — attempt checkpoint recovery return await recoverFromCheckpoint(workspaceFolder); } // 3. VALIDATE — Confirm state structure before using if (!state.$schema || state.$schema !== "agloop-state-v1") { throw new Error("Invalid state schema version"); } if ( ![ "init", "research", "plan", "critique", "execute", "verify", "complete", ].includes(state.current_phase) ) { throw new Error(`Invalid phase: ${state.current_phase}`); } // 4. MODIFY — Apply changes to the in-memory object state.current_phase = "execute"; state.current_task_id = "task-001"; state.last_action = { agent: "agloop", action: "phase_transition", timestamp: new Date().toISOString(), task_id: "task-001", }; state.phase_history.push({ phase: "execute", entered_at: new Date().toISOString(), exited_at: null, }); // 5. VALIDATE AGAIN — Confirm changes are consistent if (state.current_task_id) { const taskExists = state.tasks.some((t) => t.id === state.current_task_id); if (!taskExists) throw new Error(`Task ${state.current_task_id} not found`); } // 6. WRITE ATOMICALLY — Write to temp file, then rename const tmpPath = ".agloop/state.json.tmp"; await fs.writeFile(tmpPath, JSON.stringify(state, null, 2), "utf-8"); await fs.rename(tmpPath, ".agloop/state.json"); ``` ### Critical Rules - **Never write without reading first** — always base writes on current disk state - **Double-validate** — validate before modification AND before writing - **Atomic writes** — write to `.tmp` file then rename to prevent corruption on crash - **No partial updates** — always write the complete state object ## plan.yaml Schema Reference Produced by the Planner agent, consumed by Coordinator, Critic, Executor, and Verifier. ### Field Reference | Field | Type | Required | Constraints | | ----------------------------------- | ------ | -------- | --------------------------------------------------------------- | | `plan_id` | string | yes | Format: `plan-YYYYMMDD-HHmmss` | | `feature` | string | yes | Non-empty, verbatim user request | | `created_at` | string | yes | ISO 8601 | | `updated_at` | string | yes | ISO 8601, ≥ created_at | | `status` | enum | yes | `draft \| under_review \| approved \| in_progress \| completed` | | `pre_mortem.overall_risk_level` | enum | yes | `low \| medium \| high \| critical` | | `pre_mortem.critical_failure_modes` | array | yes | ≥1 entry | | `pre_mortem.assumptions` | array | yes | ≥1 entry | | `tasks` | array | yes | ≥1 task | | `tasks[].id` | string | yes | Format: `task-NNN`, unique | | `tasks[].depends_on` | array | yes | All referenced IDs must exist | | `tasks[].acceptance_criteria` | array | yes | ≥1 criterion per task | | `tasks[].estimated_effort` | enum | yes | `small \| medium \| large` | | `dag_edges` | array | yes | Each edge: `[from_id, to_id]`, both must exist | | `critique_history` | array | yes | Empty initially | ## log.json Append Pattern The execution log uses **JSON Lines format** — one JSON object per line, no surrounding array. This enables efficient appending without reading the entire file. ### Append Procedure ```javascript // CORRECT: Append a single line to log.json (JSON Lines format) async function appendLog(logPath, entry) { // Ensure required fields const logEntry = { timestamp: entry.timestamp || new Date().toISOString(), agent: entry.agent, action: entry.action, task_id: entry.task_id || null, phase: entry.phase, input_summary: truncate(entry.input_summary || "", 500), output_summary: truncate(entry.output_summary || "", 500), status: entry.status || "success", ...(entry.metadata ? { metadata: entry.metadata } : {}), }; // Append as a single line — do NOT overwrite const line = JSON.stringify(logEntry) + "\n"; await fs.appendFile(logPath, line, "utf-8"); } // WRONG: Do NOT do this — it reads and rewrites the entire file // const log = JSON.parse(await fs.readFile(logPath)); // log.push(entry); // await fs.writeFile(logPath, JSON.stringify(log)); ``` ### Log Entry Fields | Field | Type | Required | Constraints | | ---------------- | ------------ | -------- | ------------------------------- | | `timestamp` | string | yes | ISO 8601 with milliseconds | | `agent` | string | yes | Agent or hook identifier | | `action` | string | yes | One of the defined action types | | `task_id` | string\|null | yes | Existing task ID or null | | `phase` | string | yes | Current phase name | | `input_summary` | string | yes | Max 500 chars | | `output_summary` | string | yes | Max 500 chars |
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub