Skip to content

Journal System

The journal system provides durable audit logging for agent processes. Every state transition, trigger firing, invocation result, and checkpoint is recorded as a JournalEntry, enabling crash recovery, observability, and post-mortem analysis.

from promptise.runtime.journal import FileJournal, JournalEntry

journal = FileJournal(base_path=".promptise/journal")

await journal.append(JournalEntry(
    process_id="data-watcher",
    entry_type="trigger_event",
    data={"trigger_type": "cron", "scheduled_time": "2026-03-04T10:05:00"},
))

entries = await journal.read("data-watcher", limit=10)

Concepts

The journal sits between the agent runtime and durable storage, capturing a chronological record of everything that happens in a process. It serves two primary purposes:

  1. Observability -- view what happened, when, and why. The CLI promptise runtime logs command reads from the journal.
  2. Crash recovery -- after a crash, the ReplayEngine reads the journal, finds the last checkpoint, and replays subsequent entries to reconstruct the process state.

Three detail levels control how much is recorded:

Level What is recorded When to use
"none" Nothing Fire-and-forget processes where history does not matter
"checkpoint" State snapshots after each trigger-invoke-result cycle Default. Good balance of observability and storage efficiency
"full" Every side effect (tool calls, LLM responses, state mutations) Debugging, audit trails, compliance requirements

JournalEntry

Every journal record is a JournalEntry dataclass:

from promptise.runtime.journal import JournalEntry

entry = JournalEntry(
    process_id="data-watcher",
    entry_type="state_transition",
    data={"from_state": "created", "to_state": "running"},
)
Field Type Description
entry_id str Unique entry ID (auto-generated UUID)
process_id str Owning process identifier
timestamp datetime When the entry was recorded (UTC)
entry_type str Type of entry
data dict[str, Any] Entry-specific payload

Entry types

Entry Type Description
state_transition Process state change (from/to state)
trigger_event A trigger fired (trigger type, payload)
invocation_start Agent invocation began
invocation_result Agent invocation completed (result)
checkpoint Full state snapshot
context_update Context state key changed
error An error occurred

Serialization

data = entry.to_dict()
restored = JournalEntry.from_dict(data)

JournalLevel

Controls the detail level of journaling:

from promptise.runtime.journal import JournalLevel

JournalLevel.NONE        # No journaling
JournalLevel.CHECKPOINT  # State snapshots per cycle (default)
JournalLevel.FULL        # Every side effect

Configure via JournalConfig:

from promptise.runtime.config import JournalConfig

# Checkpoint level with file backend
JournalConfig(level="checkpoint", backend="file", path=".promptise/journal")

# Full level for debugging
JournalConfig(level="full", backend="file")

# Disabled
JournalConfig(level="none")

JournalProvider Protocol

All journal backends implement the JournalProvider protocol:

from promptise.runtime.journal import JournalProvider

class JournalProvider(Protocol):
    async def append(self, entry: JournalEntry) -> None:
        """Append an entry to the journal."""
        ...

    async def read(
        self,
        process_id: str,
        *,
        since: datetime | None = None,
        entry_type: str | None = None,
        limit: int | None = None,
    ) -> list[JournalEntry]:
        """Read entries with optional filters."""
        ...

    async def checkpoint(
        self, process_id: str, state: dict[str, Any]
    ) -> None:
        """Store a full state checkpoint."""
        ...

    async def last_checkpoint(
        self, process_id: str
    ) -> dict[str, Any] | None:
        """Return the most recent checkpoint, or None."""
        ...

    async def close(self) -> None:
        """Release any resources."""
        ...

Available Backends

Backend Class Persistence Use Case
File FileJournal Append-only JSONL files on disk Production
Memory InMemoryJournal Python list in memory Testing and development

See Journal Backends for detailed documentation of each backend.


Reading Entries

from promptise.runtime.journal import FileJournal

journal = FileJournal()

# Read all entries for a process
entries = await journal.read("data-watcher")

# Read with filters
from datetime import datetime, UTC
entries = await journal.read(
    "data-watcher",
    since=datetime(2026, 3, 4, tzinfo=UTC),
    entry_type="trigger_event",
    limit=20,
)

# Read the last checkpoint
checkpoint = await journal.last_checkpoint("data-watcher")

Checkpointing

Checkpoints store a full state snapshot that the ReplayEngine uses as a recovery point:

await journal.checkpoint("data-watcher", {
    "context_state": {"pipeline_status": "healthy", "check_count": 42},
    "lifecycle_state": "running",
    "invocation_count": 42,
})

The checkpoint is stored separately from the regular journal entries (as a dedicated file in the file backend) and also recorded as a journal entry with entry_type="checkpoint".


API Summary

Class / Enum Description
JournalEntry Single journal record dataclass
JournalLevel Detail level enum: NONE, CHECKPOINT, FULL
JournalProvider Protocol for journal backends
FileJournal Append-only JSONL file backend
InMemoryJournal In-memory backend for testing
ReplayEngine Crash recovery from journal entries

Tips and Gotchas

Use checkpoint level for production

The "checkpoint" level captures enough information for crash recovery without the storage overhead of "full". Reserve "full" for debugging specific issues.

View logs via CLI

Use promptise runtime logs <process-name> to view journal entries in a formatted table. Add --lines 50 to see more entries.

Journal entries are append-only

Neither backend supports deleting individual entries. To clear a process's journal, delete the JSONL file manually (file backend) or create a new InMemoryJournal instance.

Checkpoint state must be JSON-serializable

The checkpoint data dict is serialized to JSON. Ensure all values are JSON-compatible (strings, numbers, booleans, lists, dicts, None).


What's Next