| name | agent-state |
| description | Records what the agent does as it drives other skills -- Questions worked on, Runs executed, Results produced, Claims asserted -- in .research/state (canonical, low-frequency) and .research/events (canonical, append-only). Also models the research itself as Project/Hypothesis/Experiment/Source/Evidence, layered above Question/Run, with write-time referential integrity checks. Use when a skill wants to track its own execution history instead of writing one file per run, or when a user wants to see what the agent has been doing. Triggers on phrases like "what have you been running", "log this run", "show agent activity". |
| metadata | {"data_access_level":"raw","task_type":"open-ended"} |
Agent State
Stores nine cross-skill entities without one file per record. Project,
Question, Hypothesis, Experiment, Run, Source, and Evidence are
low-frequency, mutable, and live in id-keyed YAML maps under
.research/state/; Result and Claim are immutable facts appended to
daily JSONL shards under .research/events/.
A SQLite index under .research/indexes/ gives fast filtered queries and is
rebuilt on demand -- it is never a source of truth and can be deleted at any
time.
The research entities form a strict linear chain -- Project -> Question ->
Hypothesis -> Experiment -> Run -> Result -- with a foreign key validated at
write time on every link. A Run that supplies at least a Question (directly
or via --hypothesis-id/--experiment-id) gets any missing levels below it
auto-filled with synthetic: true placeholder records, so the chain is
never broken. A Run given none of --question/--question-id/
--hypothesis-id/--experiment-id stays fully standalone, exactly as
before this chain existed: question_id, hypothesis_id, and
experiment_id are all null, and nothing is auto-created.
--start-run accepts at most one level of that chain per call.
Combining levels (e.g. --question with --experiment-id) is rejected with
a ValueError rather than silently letting the more specific one win and
discarding the other -- which would create no Question at all from a
--question that named one.
Source and Evidence sit alongside this chain rather than inside it: a
Source belongs to a Project directly (not to the linear chain), and an
Evidence Statement links a Source to a Question (required) and optionally
a Hypothesis, letting a research finding be traced back to both where it
came from and what it bears on. Neither participates in --start-run's
synthetic-record auto-fill.
Full design rationale: docs/superpowers/specs/2026-08-05-agent-state-storage-design.md
(chain design) and docs/superpowers/specs/2026-08-06-evidence-foundation-design.md
(Source/Evidence design).
This skill has no slash command -- it's infrastructure other skills call
into, the same way resource-resolver is. It does not depend on
resource-resolver and isn't depended on by it; --artifact-role/
--artifact-path on --record-result are conventionally a resource-resolver
role name and a path relative to it, but this skill never calls resolve.py
itself.
Calling convention
STATE="$(find ~/.claude -path "*/agent-state/scripts/state.py" | head -1)"
python "$STATE" --start-run --skill deep-research --mode full \
--question "Does this need offline support?" --json
python "$STATE" --start-run --skill deep-research --question-id q_20260805_ab12cd --json
python "$STATE" --start-run --skill deep-research \
--question "Does this need offline support?" --project-id proj_20260806_ab12cd --json
python "$STATE" --complete-run --run-id run_20260805_9f3a1c --status completed --json
python "$STATE" --record-result --run-id run_20260805_9f3a1c \
--summary "Found 3 sources" --artifact-role bibliography --artifact-path sources.bib --json
python "$STATE" --record-claim --run-id run_20260805_9f3a1c \
--statement "Region X diverges from the plan" --confidence high --json
python "$STATE" --answer-question --question-id q_20260805_ab12cd --json
python "$STATE" --abandon-question --question-id q_20260805_ab12cd --json
--evidence is the CLI flag for --record-claim's supporting reference; it
is stored on the Claim event as evidence_ref (path, quote, or URL) --
the flag and field names differ deliberately, --evidence reads better on
the command line while evidence_ref makes clear in the record itself that
it's a reference, not the evidence content.
Every action except --report prints JSON on stdout, including errors
(exit code 1, a {"error": ..., "message": ...} payload) -- never a raw
traceback. Check error before trusting any other field, the same rule
resource-resolver's SKILL.md states for its own JSON output.
Research chain
python "$STATE" --create-project --name "Offline Support Initiative" \
--description "Investigate offline usage patterns." --json
python "$STATE" --create-question --question "Does this need offline support?" \
--skill research-project-init --project-id proj_20260806_ab12cd --json
python "$STATE" --create-hypothesis --question-id q_20260806_ab12cd \
--statement "Offline support is unnecessary." --skill deep-research --json
python "$STATE" --create-experiment --hypothesis-id hyp_20260806_ef34gh \
--description "Survey production traffic logs." --skill deep-research --json
python "$STATE" --start-run --skill deep-research --experiment-id exp_20260806_ij56kl --json
python "$STATE" --start-run --skill deep-research --question "New question text" --json
python "$STATE" --set-hypothesis-status --hypothesis-id hyp_20260806_ef34gh --status supported --json
python "$STATE" --set-experiment-status --experiment-id exp_20260806_ij56kl --status completed --json
python --create-source --title \
--authors --year 2025 --doi \
--skill bibliography_agent --project-id proj_20260806_ab12cd --json
python --set-source-screening --source-id src_20260806_mn78op \
--screening-status included --json
python --set-source-evidence-tier --source-id src_20260806_mn78op \
--evidence-tier --json
python --create-evidence --source-id src_20260806_mn78op \
--question-id q_20260806_ab12cd --statement --stance supports --skill synthesis_agent --json
python --record-claim --run-id run_20260806_qr12st \
--statement --confidence high \
--evidence-id evd_20260806_uv34wx --json
synthetic: true on an auto-created Hypothesis/Experiment marks it as
chain-completion scaffolding rather than a deliberately declared one --
--query/--report output can filter on it, but it behaves identically to
a hand-created record otherwise (queryable, its status can be changed).
Querying
python "$STATE" --query --run-id run_20260805_9f3a1c --json
python "$STATE" --query --question-id q_20260805_ab12cd --json
python "$STATE" --query --project-id proj_default --json
python "$STATE" --query --hypothesis-id hyp_20260806_ef34gh --json
python "$STATE" --query --experiment-id exp_20260806_ij56kl --json
python "$STATE" --query --source-id src_20260806_mn78op --json
python "$STATE" --query --skill deep-research --json
python "$STATE" --query --since 2026-08-01 --json
Exactly one filter is required per call. --query incrementally syncs the
SQLite index before reading, so results always reflect the latest recorded
state without needing an explicit --rebuild-index first.
User-facing visibility
python "$STATE" --report
python "$STATE" --report --since 2026-08-01
--report is the one non-JSON, plain-text action -- the sanctioned way for a
user to see what the agent has been doing. Nothing under .research/state/,
.research/events/, or .research/indexes/ is meant to be opened or
hand-edited directly; if a user asks what's been happening, run --report
instead of reading those files for them.
Rebuilding the index
python "$STATE" --rebuild-index --json
python "$STATE" --rebuild-index --full --json
.research/indexes/index.db is disposable, and gitignored via a
.research/.gitignore this skill bootstraps itself on first write (along
with state/*.lock, events/, and cache/) -- there's nothing to set up
by hand. If the index is ever missing or corrupted, --query and --report
already self-heal it transparently (a full rebuild happens automatically, no
user action required); run --rebuild-index --full yourself when you want
to force that regeneration on demand, e.g. right after hand-editing
state/*.yaml, from .research/state/*.yaml and .research/events/*.jsonl
-- the only two locations that are ever authoritative.
Validating referential integrity
python "$STATE" --validate --json
Every --create-hypothesis/--create-experiment/--start-run call already
rejects a dangling foreign key at write time -- --validate exists for the
case where state/*.yaml was hand-edited afterward. It reports every
dangling reference it finds ({"entity": ..., "id": ..., "field": ..., "missing_id": ...} per violation) and repairs nothing; a clean project
returns {"violations": [], "clean": true}.
Schema versioning
Every state/*.yaml file (including state/sources.yaml and
state/evidence.yaml) carries a top-level
version: field (currently 1); every events/*.jsonl line carries a
schema_version field (currently 1); indexes/index.db carries its
version in SQLite's own PRAGMA user_version (currently 3, bumped from
2 when the Source/Evidence tables and the claims.evidence_id column
were added).
A file with no version field at all (written before this mechanism existed)
is treated as version 1 -- the format it was actually written in -- not an
error. A file whose version field is present but doesn't match what this
code understands raises a loud StateParseError naming the mismatch, rather
than silently misreading it; indexes/index.db is the one exception, since
it's fully disposable -- a version mismatch there is treated the same as
corruption and triggers an automatic wipe-and-rebuild, no user action
required.
This is detection, not migration: if a future schema change needs old
state/*.yaml/events/*.jsonl data actually converted to a new shape, that
conversion logic doesn't exist yet and would need to be written when that
change happens. What exists today is the guarantee that an incompatible file
is never silently misread as if it were current.
Non-goals
- Does not migrate any existing skill (
deep-research, research-log,
report-slides, etc.) to call into this system. Adoption is separate,
per-skill follow-up work.
- Does not define a retention or pruning policy for
.research/events/ --
old shards accumulate indefinitely under this design.
- Does not copy artifact content.
--artifact-role/--artifact-path record
where a Result's output lives; the content itself stays wherever the
producing skill wrote it.
- Does not implement actual cross-version data migration for
state/*.yaml
or events/*.jsonl -- only version detection (see "Schema versioning"
above). Writing a real migration is future work, once there's a second
schema version to migrate to or from.
- Does not migrate
research-log, report-slides, academic-paper, or any
review workflow to actually call into the Project/Hypothesis/Experiment
layer -- this defines the schema and CLI contract; wiring a specific
consumer to it is separate follow-up work.
- Does not enforce a status state machine on Hypothesis/Experiment (e.g.
nothing stops moving a
completed Experiment back to running) -- status
is a label a caller sets, not a guarded lifecycle.