Skip to content

Runtime API Reference

Agent process lifecycle, triggers, governance (budget, health, mission, secrets), journals, distributed coordination, and the orchestration API.

Core

AgentProcess

promptise.runtime.process.AgentProcess

Lifecycle container for a long-running agent process.

Lifecycle (see :mod:promptise.runtime.lifecycle): :meth:start, :meth:stop, :meth:suspend / :meth:resume, automatic restarts (restart_policy) and :meth:close. Every lifecycle change emits an event when an event_notifier is set: process.started, process.stopped, process.failed (with the error), process.restarted, process.restart_exhausted, process.suspended and process.resumed (both with a reason: "manual", "idle", "budget", "health", "rewind" or, for an automatic resume, "trigger").

Trigger events are processed in arrival order. While the process is suspended they wait in the queue; at :meth:stop the events still queued (or held by a worker, or running) are moved to :attr:dead_letters with reason "process stopped", from where :meth:redeliver_dead_letters can queue them again after a restart.

Parameters:

Name Type Description Default
name str

Unique process name.

required
config ProcessConfig

Process configuration.

required
process_id str | None

Unique ID (auto-generated if not provided).

None
event_bus Any | None

Optional shared EventBus for inter-process events.

None
broker Any | None

Optional shared MessageBroker for message triggers.

None
runtime Any | None

Optional parent :class:AgentRuntime reference (enables the spawn_process meta-tool in open mode).

None
event_notifier Any | None

Optional :class:~promptise.events.EventNotifier for the process's events (and its agent's).

None
hooks HookManager | None

Optional shared :class:~promptise.runtime.hooks.HookManager (e.g. one :class:AgentRuntime passes to all its processes), dispatched after the process's own :attr:hooks (which holds config.hooks); ctx.process_id names the process.

None
deploy_policy DeployPolicy | None

Where the config came from (:class:~promptise.runtime._deploy_policy.DeployPolicy). The default is an operator config without limits; the control API passes source="api", which limits ${VAR} references in secrets to the variables the operator allowed and makes an empty open_mode.allowed_mcp_urls mean no MCP server. Processes this one spawns get the same policy.

None

Lifecycle hooks: the process dispatches every :class:~promptise.runtime.hooks.HookEvent to :attr:hooks (see the event's docstring for when, with which data, and what blocking it does). Hook data has secret values redacted.

Raises:

Type Description
ValueError

An entry of config.servers is not a server spec (see :func:_resolve_server_specs).

context property

The unified agent context.

dead_letters property

Trigger events that could not be processed, oldest first.

Each entry has event (the :class:TriggerEvent), reason ("retries exhausted", "process failed", "process stopped", "process restarting", "trigger removed", "queue full", "flagged by payload scan", "blocked by guardrails" or "blocked by hook"), error, attempts and dead_lettered_at. A stop dead-letters every event not run yet: queued, held by a worker or a trigger listener, or still inside a trigger (a file watch's backlog, a webhook's accepted requests). The list keeps the most recent trigger_delivery.dead_letter_size entries.

hooks property

This process's :class:~promptise.runtime.hooks.HookManager.

Holds the hooks of config.hooks; register more in code::

@process.hooks.on(HookEvent.USER_PROMPT_SUBMIT)
async def audit(ctx):
    ...

Its default hook timeout is 30 seconds. A hook must not await a lifecycle method of this process (stop(), start(), rewind()); schedule it with asyncio.create_task.

lifecycle property

The lifecycle state machine (for inspection).

recovered property

What the first :meth:start restored from the journal, or None.

Keys: lifecycle_state (the state the journal last recorded: "stopped" after a clean stop, "running" after a crash), entries_replayed, context_keys, mission_restored, budget_restored, conversation_messages and invocation_count.

spawned_processes property

Names of the processes this one spawned (open mode).

state property

Current process state.

suspend_reason property

Why the process is suspended ("manual", "idle", "budget", "health", "rewind"), or None when it isn't.

add_dynamic_trigger(config) async

Add a trigger on the agent's behalf (open mode's add_trigger).

Unlike :meth:add_trigger the trigger is not added to config.triggers: it lives until :meth:stop, :meth:rollback or :meth:remove_trigger, and counts towards open_mode.max_dynamic_triggers. It starts at once with its listener (and filter). A CONFIG_CHANGE hook (source="agent", change="trigger_added") can refuse it.

Returns:

Type Description
str

The trigger's ID.

Raises:

Type Description
RuntimeError

Not in open mode, or the process is stopping, stopped or failed.

PermissionError

open_mode.allow_trigger_management is off, a hook refused it, or it is a webhook on a non-loopback host (see :meth:_agent_trigger_problem).

ValueError

The limit is reached, or the config is invalid or can't start.

add_trigger(config) async

Add a config trigger; it starts at once if the process is active.

The trigger is appended to config.triggers. A RUNNING or SUSPENDED process creates and starts it immediately (its events queue while suspended); otherwise it starts with the next :meth:start.

Parameters:

Name Type Description Default
config TriggerConfig | dict[str, Any]

A :class:TriggerConfig (or a dict of its fields).

required

Returns:

Type Description
str

The trigger's stable ID (see :func:config_trigger_ids).

Raises:

Type Description
ValueError

The config is invalid or the trigger can't be created or started. Nothing is added.

apply_governance_config(section)

Apply config.<section> to the running process now.

Call it after changing config.budget, config.health or config.mission (the orchestration API's PATCH routes do). Enabling a subsystem builds it, disabling it drops it, and new values of an enabled one apply from its next check: the budget keeps its counters, the health monitor its history (:meth:HealthMonitor.apply_config) and the mission its progress. The next invocation uses the updated callbacks; one already running finishes with the old ones.

Parameters:

Name Type Description Default
section str

"budget", "health" or "mission".

required

Raises:

Type Description
ValueError

section is not one of those.

ask(content, *, sender_id=None, timeout=120) async

Ask the agent a question and wait for the response.

The question is delivered to the agent on its next invocation. This method blocks until the agent responds or the timeout expires.

Parameters:

Name Type Description Default
content str

The question text.

required
sender_id str | None

Who is asking.

None
timeout float

Maximum seconds to wait for an answer.

120

Returns:

Name Type Description
An Any

Raises:

Type Description
RuntimeError

If inbox is not enabled.

TimeoutError

If timeout expires.

InboxError

The question was refused, or removed before it was answered (InboxQuestionRemoved after a clear, InboxFullError when evicted); see :meth:MessageInbox.wait_for_response.

clear_dead_letters()

Drop all dead letters. Returns how many were removed.

close() async

Stop the process and release what lives as long as the object.

Calls :meth:stop, then closes the long-term memory provider, revokes secrets (whatever revoke_on_stop says) and clears the conversation history. async with process: and :meth:AgentRuntime.remove_process call it. A closed process can be started again: :meth:start opens a new memory provider (an in_memory one starts empty).

fail_mission(reason)

Fail the mission (an operator decision) and emit mission.failed.

The reason is stored with the mission (failure_reason, the [Mission] prompt block, the journal). The event carries reason: "manual" and message: reason.

Parameters:

Name Type Description Default
reason str

Why the mission failed.

required

Returns:

Type Description
dict[str, Any]

The mission.failed event data.

Raises:

Type Description
RuntimeError

The process has no mission (mission.enabled is off).

ValueError

The mission is already completed or failed.

inject(event) async

Manually inject a trigger event into the queue.

Like a trigger event, it resumes a process suspended for idle_timeout, and passes the FILE_CHANGED / TASK_CREATED hooks (a hook can refuse it). A full queue dead-letters it.

Parameters:

Name Type Description Default
event TriggerEvent

The trigger event to inject.

required
record_operator_action(action, data) async

Journal an operator action (an API request, a dashboard command).

Written as an action entry (for example "api_request") in the process's journal, with secret values redacted. Does nothing when the process has no journal; a journal failure is logged, never raised.

Parameters:

Name Type Description Default
action str

The entry type.

required
data dict[str, Any]

JSON-serialisable details.

required
redeliver_dead_letters(event_ids=None) async

Put dead-lettered events back on the queue.

Parameters:

Name Type Description Default
event_ids list[str] | None

Only redeliver these events (default: all).

None

Returns:

Type Description
int

Number of events re-queued. Events that don't fit in the

int

queue stay in the dead-letter list.

remove_trigger(trigger_id, *, source='operator') async

Stop and remove a trigger: a config trigger or an agent-added one.

A config trigger (ID from :func:config_trigger_ids) is removed from config.triggers and, when it runs, stopped with its listener at once. An agent-added (open mode) trigger is stopped, its listener cancelled, and dropped. The other triggers keep their IDs. Dispatches CONFIG_CHANGE (change="trigger_removed").

Parameters:

Name Type Description Default
trigger_id str

The trigger's ID.

required
source str

"operator" (default) or "agent" (the open-mode remove_trigger meta-tool): an agent can only remove triggers it added, and a CONFIG_CHANGE hook can refuse it.

'operator'

Raises:

Type Description
KeyError

No trigger has trigger_id (for source="agent": no agent-added one).

PermissionError

A hook refused the agent's change.

resume(*, reason='manual') async

Resume a SUSPENDED process (or one in the reserved AWAITING).

Queued events run in arrival order, and the idle clock restarts, so idle_timeout counts from the resume. Emits process.resumed with the reason and suspended_for (the reason of the suspension).

Parameters:

Name Type Description Default
reason str

event woke a process suspended for idle_timeout) or "rewind" (the end of a :meth:rewind).

'manual'

Raises:

Type Description
StateError

The process is not suspended.

rewind(target_entry_id, mode='both', *, actor='operator', wait_timeout=60.0) async

Roll the process back to just before a journal entry.

Modes (:class:~promptise.runtime.journal.RewindMode): "both" (context state and conversation history), "conversation_only", "code_only" (the context state only), "summarize" (nothing is rolled back; a system note about the interval is added to the conversation) and "cancel" (a dry run, like :meth:rewind_plan).

Non-destructive: the journal is only appended to — a rewind entry (with actor) and a checkpoint of the rewound state, so a later recovery starts from it. Never rolled back: long-term memory, the budget counters (a rewind doesn't refund budget), the mission, secrets, and the effects tool calls had outside the process.

A RUNNING process is suspended for the rewind (reason "rewind"), waits for a run in flight to finish, and is resumed afterwards; events that arrive meanwhile wait in the queue. A suspended or stopped process stays as it is. Emits process.rewound and the CONFIG_CHANGE hook (change="rewind").

Different from :meth:rollback (open mode), which discards the agent's self-modifications (instructions, tools, servers, triggers) and keeps the state and history.

Parameters:

Name Type Description Default
target_entry_id str

The journal entry to go back to (from GET /processes/{name}/journal or :meth:JournalProvider.read); the state is rebuilt as it was just before it.

required
mode Any

The rewind mode.

'both'
actor str

Who asked (recorded in the journal).

'operator'
wait_timeout float

Seconds to wait for a run in flight.

60.0

Returns:

Name Type Description
The Any

Raises:

Type Description
RuntimeError

The process has no journal.

ValueError

Unknown entry or mode.

TimeoutError

A run was still in flight after wait_timeout (nothing was changed; the process is resumed).

rewind_plan(target_entry_id, mode='both') async

Preview :meth:rewind without changing anything.

Returns:

Name Type Description
A Any
Any

journal entries after the target the rewind would undo).

Raises:

Type Description
RuntimeError

The process has no journal.

ValueError

target_entry_id is not in the journal, or mode is not a :class:~promptise.runtime.journal.RewindMode.

rollback() async

Revert to the original configuration (open mode).

Clears all dynamic state (instructions, tools, servers, triggers) and rebuilds the agent from the original config. It undoes the agent's self-modifications; it doesn't touch the context state or the conversation history. To roll those back to an earlier point, use :meth:rewind (any mode). A rollback is not refused by OpenModeConfig.max_rebuilds (it is how an operator undoes the agent's changes), but it counts as a rebuild. If the rebuild fails, the instructions, tools and servers are restored and the previous agent is kept.

Returns:

Type Description
str

Status message.

Raises:

Type Description
RuntimeError

If called in strict mode.

send_message(content, *, message_type='context', priority='normal', sender_id=None, ttl=None, metadata=None) async

Send a human message to the running agent.

The agent sees the message as additional context on its next invocation cycle.

Parameters:

Name Type Description Default
content str

Message text.

required
message_type str

One of "directive", "context", "question", "correction".

'context'
priority str

"low", "normal", "high", "critical".

'normal'
sender_id str | None

Who sent it (for audit trail).

None
ttl float | None

Time-to-live in seconds.

None
metadata dict[str, Any] | None

Custom data.

None

Returns:

Type Description
str

The message ID.

Raises:

Type Description
RuntimeError

If inbox is not enabled, or the process is stopped or failed.

ValueError

The message was refused: invalid, rate limit exceeded, inbox full, or flagged by the guardrail scanner (inbox.scan_with_guardrails); see :class:~promptise.runtime.inbox.InboxError.

set_dynamic_instructions(instructions) async

Replace the instructions on the agent's behalf (open mode).

What the modify_instructions meta-tool calls: the agent is rebuilt with instructions (hot reload), :meth:rollback restores the configured ones, and open_mode.max_instruction_length / max_rebuilds apply. A CONFIG_CHANGE hook (source="agent") can refuse it. Operators use :meth:update_instructions instead.

Returns:

Type Description
str

"Rebuild successful" or an "Error: ..." message.

Raises:

Type Description
RuntimeError

Not in open mode.

spawn_process(name, *, instructions, model=None, triggers=None, execution_mode=ExecutionMode.STRICT, runtime=None) async

Spawn a process in the runtime on the agent's behalf (open mode).

What the spawn_process meta-tool calls. The new process is registered in the parent :class:AgentRuntime and started; it inherits this process's guardrails (see :meth:_child_config), its hooks (the parent's :attr:hooks, and what the parent itself inherited, are dispatched for the child after its own, so permission_request and user_prompt_submit hooks govern the child too) and its deploy policy (a child of an API-deployed process is limited like its parent). Webhook triggers for the child must listen on loopback unless open_mode.allow_public_webhooks is on (then with an hmac_secret). Dispatches SUBAGENT_START first (a hook can refuse the spawn) and SUBAGENT_STOP when the child stops or fails. runtime overrides the process's own runtime reference.

Raises:

Type Description
RuntimeError

Not in open mode, or no runtime.

PermissionError

open_mode.allow_process_spawn is off, a hook refused the spawn, or a trigger is a webhook on a non-loopback host.

ValueError

max_spawned_processes reached, the name is taken, or the config is invalid.

Exception

The child failed to start (it is removed again).

start() async

Build agent, start triggers, begin processing.

Transitions: CREATED → STARTING → RUNNING (or STOPPED/FAILED → STARTING → RUNNING on restart).

Starting a FAILED process first tears down what the failed run left (trigger listeners, triggers, heartbeat, agent), and cancels a pending automatic restart, so nothing runs twice.

On the first start of the process object, when the journal holds a checkpoint for this process name and journal.recover_on_start is on (the default), the recorded context state, mission, conversation history and invocation count are restored first (see :attr:recovered).

Raises:

Type Description
StateError

If the transition is invalid (a :class:~promptise.runtime.exceptions.ProcessStateError and a :class:ValueError).

status()

Serializable status snapshot.

Returns:

Type Description
dict[str, Any]

Dict with process name, state, suspend_reason, counters,

dict[str, Any]

queue and dead-letter sizes, uptime_seconds (since the

dict[str, Any]

last start; None while stopped or failed), triggers

dict[str, Any]

(id, type — the config type, e.g. "cron" —,

dict[str, Any]

source and, for a file watch, dropped_events

dict[str, Any]

and pending_events), recovered (after a journal

dict[str, Any]

recovery) and, per enabled subsystem, budget,

dict[str, Any]

health_anomalies and health

dict[str, Any]

(:meth:HealthMonitor.health_status), mission_state and

dict[str, Any]

active_secrets; hook_count is the number of hooks on

dict[str, Any]
stop() async

Gracefully stop: cancel workers, stop triggers, shutdown agent.

Transitions: RUNNING / SUSPENDED / STARTING / FAILED → STOPPING → STOPPED (emits process.stopped), or CREATED → STOPPED for a process that never started (no event). Stopping a stopped process does nothing.

Trigger events still queued, held by a suspended worker or running are moved to :attr:dead_letters (reason "process stopped"); a run in flight is cancelled. Secrets are revoked unless secrets.revoke_on_stop is False. Long-term memory, the context state and the conversation history are kept (unless context.conversation_clear_on_stop), so a later :meth:start continues where this one stopped; :meth:close releases them.

An explicit stop also cancels any pending automatic restart.

suspend(*, reason='manual') async

Pause processing without tearing down the agent.

Triggers continue to fire; their events wait in the queue, in order, and run after :meth:resume. A run in flight finishes. Emits process.suspended with the reason.

Parameters:

Name Type Description Default
reason str

dashboard), "idle" (idle_timeout; the next trigger event resumes the process by itself), "budget", "health" or "rewind" (:meth:rewind suspends a running process while it rewinds, then resumes it).

'manual'

Raises:

Type Description
StateError

The process is not RUNNING.

update_instructions(instructions)

Replace the agent's instructions from the next invocation on.

The next invocation rebuilds the agent with instructions before it runs (reusing the connected MCP tools); an invocation already running finishes with the old instructions. A process that is not running uses them when it starts. They replace any instructions an open-mode agent set for itself, and :meth:rollback keeps them.

Parameters:

Name Type Description Default
instructions str

The new system prompt.

required

AgentRuntime

promptise.runtime.runtime.AgentRuntime

Multi-process agent runtime manager.

Manages a dictionary of :class:AgentProcess instances with shared resources and coordinated lifecycle.

The processes declared in config.processes are registered at construction (not started): AgentRuntime(config) and :meth:from_config are equivalent.

async with AgentRuntime(...) stops and closes every process on exit. Signals are not handled there: use :meth:run_forever (or call :meth:request_shutdown from your own handler) so SIGTERM shuts the runtime down cleanly instead of killing it mid-run.

Distributed mode: with config.distributed.enabled, :meth:start_all (and :meth:run_forever) first start this node's management transport and, per the config, the cluster coordinator or the registration with one (see :class:~promptise.runtime.config.DistributedConfig; :attr:cluster). :meth:shutdown stops them.

Parameters:

Name Type Description Default
config RuntimeConfig | None

Optional :class:RuntimeConfig for global settings and the processes to register.

None
event_bus Any | None

Optional shared EventBus for inter-process events.

None
broker Any | None

Optional shared MessageBroker for message triggers.

None
event_notifier Any | None

Optional :class:~promptise.events.EventNotifier shared by all processes (drained by :meth:stop_all).

None
hooks Any | None

Optional :class:~promptise.runtime.hooks.HookManager shared by all processes: its hooks fire for every process's lifecycle events (after each process's own hooks; ctx.process_id names the process).

None
cluster property

The running :class:~promptise.runtime.distributed.node.ClusterNode (its transport, coordinator, node_id, url), or None when distributed mode is off or not started.

processes property

Read-only view of registered processes.

add_process(name, config, *, process_id=None, deploy_policy=None) async

Register a new agent process.

Parameters:

Name Type Description Default
name str

Unique process name.

required
config ProcessConfig

Process configuration.

required
process_id str | None

Optional explicit process ID.

None
deploy_policy Any | None

Where the config came from (see :class:~promptise.runtime.process.AgentProcess); the control API passes source="api" for its deploys.

None

Returns:

Type Description
AgentProcess

The created :class:AgentProcess.

Raises:

Type Description
RuntimeBaseError

If a process with this name already exists.

from_config(config, *, event_bus=None, broker=None, event_notifier=None, hooks=None) async classmethod

Create a runtime from a :class:RuntimeConfig with all processes pre-registered.

Equivalent to AgentRuntime(config, ...), which registers the configured processes too (kept for backward compatibility).

Parameters:

Name Type Description Default
config RuntimeConfig

Runtime configuration.

required
event_bus Any | None

Optional shared EventBus.

None
broker Any | None

Optional shared MessageBroker.

None
event_notifier Any | None

Optional :class:EventNotifier for all processes.

None
hooks Any | None

Optional :class:HookManager shared by all processes.

None

Returns:

Type Description
AgentRuntime

A new :class:AgentRuntime with processes registered.

get_process(name)

Get a process by name.

Parameters:

Name Type Description Default
name str

Process name.

required

Returns:

Name Type Description
The AgentProcess

Raises:

Type Description
KeyError

If the process does not exist.

list_processes()

List all processes with summary info.

Returns:

Type Description
list[dict[str, Any]]

One dict per process: name, state (the

list[dict[str, Any]]
list[dict[str, Any]]
load_directory(directory) async

Load all .agent files from a directory.

Parameters:

Name Type Description Default
directory str | Path

Directory containing .agent files.

required

Returns:

Type Description
list[str]

List of process names that were loaded.

Raises:

Type Description
ManifestError

If any manifest fails to load.

load_manifest(path, *, name_override=None) async

Load and register a process from a .agent manifest.

Parameters:

Name Type Description Default
path str | Path

Path to the .agent file.

required
name_override str | None

Override the process name from the manifest.

None

Returns:

Type Description
AgentProcess

The created :class:AgentProcess.

Raises:

Type Description
ManifestError

If the manifest cannot be loaded.

process_status(name)

Status for a single process.

Parameters:

Name Type Description Default
name str

Process name.

required

Returns:

Type Description
dict[str, Any]

Process status dict.

Raises:

Type Description
KeyError

If the process does not exist.

remove_process(name) async

Remove a process, stopping and closing it first.

Works in every state: a FAILED process is stopped too, so its triggers, listeners and heartbeat end before it is removed. The process is then closed (:meth:AgentProcess.close: long-term memory closed, secrets revoked).

Parameters:

Name Type Description Default
name str

Process name to remove.

required

Raises:

Type Description
KeyError

If the process does not exist.

request_shutdown()

Ask :meth:run_forever to stop (safe from a signal handler).

restart_process(name, *, stop_timeout=30.0) async

Restart a single process (stop then start).

A running or suspended process is stopped first; a FAILED one is torn down by :meth:AgentProcess.start itself (its old triggers, listeners and heartbeat end before new ones start), so nothing fires twice. Context state, long-term memory and the conversation history carry over.

A process that is already stopping (another stop or restart in progress) is waited for, up to stop_timeout seconds.

Parameters:

Name Type Description Default
name str

Process name.

required
stop_timeout float

Seconds to wait for a stop in progress.

30.0

Raises:

Type Description
KeyError

If the process does not exist.

RuntimeError

The process was still stopping after stop_timeout seconds (nothing was started).

run_forever(*, install_signal_handlers=True) async

Start all processes and run until shutdown is requested.

Returns after :meth:request_shutdown, SIGTERM or SIGINT (with install_signal_handlers), or when the task running it is cancelled; every process is then stopped and closed (:meth:shutdown), so docker stop or Ctrl+C never cuts a process off without process.stopped, a final checkpoint and its dead letters.

Parameters:

Name Type Description Default
install_signal_handlers bool

Install SIGTERM / SIGINT handlers on the running loop for the duration of the call (removed afterwards: SIGTERM goes back to the default, SIGINT to KeyboardInterrupt). Skipped where the loop doesn't support them (Windows, or a thread other than the main one).

True
shutdown() async

Stop every process (:meth:stop_all), then close them all.

Closing releases what a process keeps across restarts (long-term memory, secrets, conversation history). The processes stay registered. The cluster node (distributed mode) stops last.

start_all() async

Start all registered processes that are not running.

CREATED, STOPPED and FAILED processes are started (a failed one is torn down first, see :meth:AgentProcess.start). A process that fails to start is logged and skipped. With distributed.enabled the cluster node starts first (:meth:start_distributed); its failure raises.

start_distributed() async

Start this node's cluster membership (distributed.enabled).

Starts the management transport on distributed.transport_host:transport_port, then the coordinator (coordinator=True) or the registration with coordinator_url. Does nothing when distributed.enabled is off or it already runs. :meth:start_all calls it.

Raises:

Type Description
ValueError

The cluster token's ${ENV_VAR} is not set.

OSError

The transport port can't be bound.

start_process(name) async

Start a single process.

Parameters:

Name Type Description Default
name str

Process name.

required

Raises:

Type Description
KeyError

If the process does not exist.

status()

Global runtime status.

Returns:

Type Description
dict[str, Any]

Dict with process count and per-process status (plus

dict[str, Any]

cluster in distributed mode).

stop_all() async

Stop all processes that are not stopped (FAILED ones too).

Afterwards the shared event notifier (if any) is drained, so every process.stopped event is delivered before this returns.

stop_distributed() async

Stop the cluster membership (unregister, stop the transport).

stop_process(name) async

Stop a single process.

Parameters:

Name Type Description Default
name str

Process name.

required

Raises:

Type Description
KeyError

If the process does not exist.

AgentContext

promptise.runtime.context.AgentContext

Unified context layer for an agent process.

Provides:

  • .get() / .put() — persistent key-value state with audit trail
  • .memory — optional :class:~promptise.memory.MemoryProvider
  • .env — filtered environment variables
  • .files — mounted file paths

Every successful write (:meth:put, :meth:update) is recorded in the key's audit history and handed to the hooks registered with :meth:add_update_hook. :meth:restore_state loads recovered state without firing them.

Parameters:

Name Type Description Default
writable_keys list[str] | None

Keys that :meth:put and :meth:update may write (agent, API and trigger writes alike). None or an empty list means all keys are writable.

None
memory_provider Any | None

Optional :class:~promptise.memory.MemoryProvider instance.

None
file_mounts dict[str, str] | None

Mapping of logical name → filesystem path.

None
env_prefix str

Only expose env vars starting with this prefix.

'AGENT_'
initial_state dict[str, Any] | None

Pre-populated key-value state.

None
env property

Filtered snapshot of environment variables.

Only variables whose name starts with :attr:env_prefix are included. The prefix is not stripped from the key.

files property

Mapping of logical mount name → filesystem path.

memory property

The underlying :class:~promptise.memory.MemoryProvider, or None.

writable_keys property

The keys :meth:put may write (sorted), or None when all are.

add_memory(content, *, metadata=None) async

Store a new memory.

Parameters:

Name Type Description Default
content str

Text content to store.

required
metadata dict[str, Any] | None

Optional metadata.

None

Returns:

Type Description
str | None

Memory ID string, or None if no provider is configured.

add_update_hook(hook)

Call hook with the :class:StateEntry of every state write.

Meant for journaling: an owner registers a hook that appends a context_update entry (:data:CONTEXT_UPDATE_ENTRY_TYPE) with entry.to_dict() as its data, which ReplayEngine replays after the last checkpoint. The hook runs synchronously after the write; if it returns an awaitable, that is scheduled on the running event loop (await :meth:flush_update_hooks to wait for it). A hook that raises is logged and never fails the write.

Returns:

Type Description
Callable[[], None]

A function that removes the hook.

can_write(key)

Whether key may be written by :meth:put / :meth:update.

clear_state()

Remove all state and history.

delete_memory(memory_id) async

Delete a memory by ID.

Returns:

Type Description
bool

True if deleted, False if no provider or not found.

denied_keys(keys)

The keys among keys that may not be written, in the given order.

flush_update_hooks() async

Wait for the awaitables returned by update hooks to finish.

from_dict(data, *, memory_provider=None) classmethod

Reconstruct an :class:AgentContext from a serialized dict.

Parameters:

Name Type Description Default
data dict[str, Any]

Dict produced by :meth:to_dict.

required
memory_provider Any | None

Optional provider to re-attach.

None

Returns:

Name Type Description
Restored AgentContext
get(key, default=None)

Read a value from the state store.

Parameters:

Name Type Description Default
key str

State key.

required
default Any

Value to return if key is absent.

None

Returns:

Type Description
Any

The stored value or default.

put(key, value, *, source='agent')

Write a value to the state store.

The write is recorded in the key's audit history and handed to the update hooks (see :meth:add_update_hook).

Parameters:

Name Type Description Default
key str

State key.

required
value Any

Value to store.

required
source str

Origin of the write ("agent", "api", "trigger", ...), kept in the audit history.

'agent'

Raises:

Type Description
KeyError

If key is not in the writable keys set (when writable keys are configured).

restore_state(state, *, source='recovery')

Load recovered state (from a checkpoint or ReplayEngine).

Bypasses :attr:writable_keys (the values were written legitimately before) and does not fire the update hooks, so restored values are not journaled a second time. Each key gets a history entry with source.

search_memory(query, *, limit=5, min_score=0.0) async

Search semantic memory for relevant context.

Parameters:

Name Type Description Default
query str

Search query text.

required
limit int

Max results to return.

5
min_score float

Min relevance score (0.0–1.0).

0.0

Returns:

Type Description
list[Any]

List of :class:~promptise.memory.MemoryResult objects,

list[Any]

or empty list if no provider is configured.

state_history(key)

Return the audit trail for a given key.

Parameters:

Name Type Description Default
key str

State key.

required

Returns:

Type Description
list[StateEntry]

Chronological list of :class:StateEntry records.

state_keys()

Return all keys in the current state.

state_snapshot()

Return a shallow copy of the current state.

to_dict()

Serialize context state for checkpointing or distribution.

Note

The memory provider is not serialized — it must be re-attached when restoring from a checkpoint.

update(values, *, source='agent')

Write several keys at once, all or nothing.

Every key is checked before anything is written: if one is not writable, none is.

Parameters:

Name Type Description Default
values Mapping[str, Any]

Keys and the values to store.

required
source str

Origin of the writes.

'agent'

Returns:

Type Description
list[str]

The keys written, in the given order.

Raises:

Type Description
KeyError

If any key is not writable (the message names all of them). Nothing is written.

StateEntry

promptise.runtime.context.StateEntry dataclass

Audit record for a single state write.

Attributes:

Name Type Description
key str

The key that was written.

value Any

The value that was stored.

timestamp float

Unix timestamp of the write.

source str

Origin of the write ("system", "agent", "trigger", etc.).

from_dict(data) classmethod

Deserialize from a dict.

to_dict()

Serialize to a JSON-compatible dict.

This is also the data of a context_update journal entry: :class:~promptise.runtime.journal.ReplayEngine reads its key and value.

ConversationBuffer

promptise.runtime.conversation.ConversationBuffer dataclass

Rolling buffer of conversation messages (short-term memory).

Maintains the last max_messages messages across invocations. Oldest messages are evicted when the buffer is full.

The buffer is safe for concurrent async access: the async helpers :meth:async_snapshot and :meth:async_replace use an internal :class:asyncio.Lock. The synchronous methods (:meth:append, :meth:get_messages, etc.) are lock-free and intended for single- threaded or test use.

Parameters:

Name Type Description Default
max_messages int

Maximum messages to retain. Oldest are evicted. 0 means unlimited.

100
enabled bool

False turns the buffer off: nothing is retained, so each invocation starts without history.

True
append(message)

Add a single message to the buffer.

Parameters:

Name Type Description Default
message dict[str, Any]

Message dict with role and content keys.

required
async_append(message) async

Thread-safe append (async).

Parameters:

Name Type Description Default
message dict[str, Any]

Message dict to append.

required
async_replace(messages) async

Thread-safe replacement of the entire buffer (async).

Parameters:

Name Type Description Default
messages list[dict[str, Any]]

New message list.

required
async_snapshot() async

Thread-safe snapshot of all messages (async).

Returns:

Type Description
list[dict[str, Any]]

Copy of the current message list.

clear()

Clear all messages from the buffer.

extend(messages)

Add multiple messages to the buffer.

Parameters:

Name Type Description Default
messages list[dict[str, Any]]

List of message dicts.

required
from_dict(data) classmethod

Reconstruct from a serialized dict.

Parameters:

Name Type Description Default
data dict[str, Any]

Dict produced by :meth:to_dict.

required

Returns:

Name Type Description
Restored ConversationBuffer
get_messages()

Return all buffered messages as a list.

Returns:

Type Description
list[dict[str, Any]]

Ordered list of message dicts (oldest first).

set_messages(messages)

Replace the buffer contents.

Used during hot-reload to preserve conversation across agent rebuilds.

Parameters:

Name Type Description Default
messages list[dict[str, Any]]

New message list to set.

required
to_dict()

Serialize for journal checkpointing.

Returns:

Type Description
dict[str, Any]

Dict with max_messages and messages keys.


Configuration

ProcessConfig

promptise.runtime.config.ProcessConfig

Bases: BaseModel

Configuration for a single agent process.

Composes all atomic configs and adds process-level settings.

Attributes:

Name Type Description
model str

LLM model identifier (e.g. openai:gpt-5-mini).

instructions str | None

System prompt for the agent.

servers dict[str, Any]

MCP server specifications (same format as .superagent).

triggers list[TriggerConfig]

Trigger configurations.

journal JournalConfig

Journal configuration. Off by default (level="none"); pass a :class:JournalConfig to record transitions, invocations and checkpoints (JournalConfig() itself defaults to level="checkpoint").

context ContextConfig

AgentContext configuration.

concurrency int

Max concurrent trigger invocations.

heartbeat_interval float

Heartbeat period in seconds.

idle_timeout float

Seconds of inactivity before suspending (0 = never).

max_lifetime float

Max process lifetime in seconds (0 = unlimited).

max_consecutive_failures int

Consecutive failures before FAILED state.

restart_policy Literal['always', 'on_failure', 'never']

When to restart the process automatically. on_failure restarts it after it enters FAILED (e.g. max_consecutive_failures reached); always also restarts it when it stops itself at max_lifetime. An explicit :meth:~AgentProcess.stop never triggers a restart.

max_restarts int

Max consecutive restart attempts after failures. The count resets after a successful invocation.

restart_backoff float

Delay in seconds before the first restart attempt; doubles on each consecutive attempt (capped at 60s).

trigger_delivery TriggerDeliveryConfig

Retries, dead-letter list and payload scanning for trigger events.

guardrails Any | None

Passed through to :func:~promptise.agent.build_agent when the agent is built, as are observe, cache, optimize_tools, adaptive and max_invocation_time.

hooks list[HookConfig]

Runtime lifecycle hooks (:class:HookConfig), registered on the process's :attr:~AgentProcess.hooks manager when the process is created.

Unknown keys are rejected, so a misspelt option fails loudly instead of being ignored.

RuntimeConfig

promptise.runtime.config.RuntimeConfig

Bases: _WarnOnUnknownKeys

Top-level runtime configuration (composite).

Aggregates per-process configs and global settings.

Attributes:

Name Type Description
processes dict[str, ProcessConfig]

Named process configurations.

distributed DistributedConfig

Distributed coordination settings.

Unknown keys are ignored with a FutureWarning.

from_dict(data) classmethod

Deserialize from a dict.

to_dict()

Serialize to a JSON-compatible dict.

Secrets (TriggerConfig.hmac_secret) are written as "**********"; supply them again when loading the dict.

DistributedConfig

promptise.runtime.config.DistributedConfig

Bases: _WarnOnUnknownKeys

Distributed runtime coordination (RuntimeConfig.distributed).

With enabled=True, :meth:AgentRuntime.start_all (and :meth:~AgentRuntime.run_forever) start this node's management transport (:class:~promptise.runtime.distributed.RuntimeTransport) on transport_host:transport_port; :meth:AgentRuntime.shutdown stops it. Then, depending on the role:

  • coordinator=True: this runtime hosts the cluster coordinator (:class:~promptise.runtime.distributed.RuntimeCoordinator), which monitors the nodes' health. With discovery_method="registry" nodes register themselves at POST /cluster/nodes of its transport (and are dropped when they stop re-registering for node_timeout seconds); with "static" the nodes are listed in static_nodes. The coordinator also lists itself.
  • coordinator_url set: this node registers with the coordinator at that URL when it starts, re-registers every heartbeat_interval seconds (so a restarted coordinator finds it again) and unregisters at shutdown.
  • Neither: a stand-alone node, managed through its transport (a static coordinator elsewhere can list it).

Security: one cluster token (auth_token) authenticates everything: requests to this node's transport (every route but GET /health), this node's registration with the coordinator, and the coordinator's requests to the nodes. A non-loopback transport_host or a coordinator on another host needs it, unless allow_unauthenticated=True says a proxy or a private network authenticates callers.

Attributes:

Name Type Description
enabled bool

Start the transport (and coordination) with the runtime.

coordinator bool

This runtime hosts the coordinator.

coordinator_url str | None

Base URL of the coordinator's transport, for discovery_method="registry" nodes.

node_id str | None

This node's ID (default: <hostname>-<port>).

transport_host str

Interface the transport binds (default loopback).

transport_port int

Port of the transport (0: a free port).

advertise_url str | None

URL other nodes reach this one at (default http://<transport_host>:<port>; required when binding a wildcard address and registering with a coordinator).

auth_token SecretStr | None

The cluster bearer token, or an ${ENV_VAR} reference resolved when the transport starts.

allow_unauthenticated bool

Allow a non-loopback transport or coordinator without auth_token.

discovery_method Literal['registry', 'static', 'multicast']

"registry" (nodes register with the coordinator) or "static" (static_nodes). "multicast" is not implemented and is rejected.

static_nodes dict[str, str]

node_id -> URL of the nodes, for a static coordinator.

allowed_node_hosts list[str]

For a registry coordinator: the host names or IP addresses a registering node's URL may name (*. + a domain matches its subdomains). Empty: any host. The coordinator sends the cluster token to every registered URL (health checks, management calls), so a token holder could point it anywhere; list your nodes' hosts to stop that.

heartbeat_interval float

Seconds between the coordinator's health checks and between a node's re-registrations.

node_timeout float | None

Seconds without a successful health check (or a re-registration) before a node counts as unhealthy (or is dropped from the registry). Default 3 * heartbeat_interval.

Raises:

Type Description
ValueError

An inconsistent combination (each message says what to change). In 1.4.3 and earlier these fields were read by nothing.

Unknown keys are ignored with a FutureWarning.

effective_node_timeout()

node_timeout, or three heartbeat intervals.

ContextConfig

promptise.runtime.config.ContextConfig

Bases: _WarnOnUnknownKeys

AgentContext configuration.

Attributes:

Name Type Description
writable_keys list[str]

State keys the agent is allowed to write to. Empty list means all keys are writable.

memory_provider Literal['in_memory', 'chroma', 'mem0'] | None

Memory provider type (or None to disable).

memory_max int

Max memories to inject per invocation.

memory_min_score float

Min relevance score for memory injection.

memory_timeout float

Seconds to wait for a memory search or auto-store write (a slower write finishes in the background).

memory_auto_store bool

Automatically store exchanges in long-term memory.

memory_collection str

Collection name for ChromaDB backend.

memory_persist_directory str | None

Persist directory for ChromaDB.

memory_user_id str

User ID for Mem0 scoping.

conversation_max_messages int

Max messages in conversation buffer (short-term memory). 0 means unlimited, not disabled.

conversation_history bool

Set to False to turn short-term memory off, so every run sees only the current trigger event.

conversation_clear_on_stop bool

Clear the conversation history when the process stops. False (default): the history survives stop() / start() and restarts of the same process object; :meth:~AgentProcess.close always clears it.

state_tool bool

Give the agent an update_state(key, value) tool that writes to its context state (limited to writable_keys when set). Off by default: without writable_keys the agent can write any key.

file_mounts dict[str, str]

Mapping of logical name → filesystem path.

env_prefix str

Only expose environment variables with this prefix (:meth:AgentContext.get_env). Must not be empty: an empty prefix would expose the whole environment.

initial_state dict[str, Any]

Pre-populated key-value state.

Unknown keys are ignored with a FutureWarning.

ExecutionMode

promptise.runtime.config.ExecutionMode

Bases: str, Enum

Agent execution mode.

Attributes:

Name Type Description
STRICT

Agent cannot modify itself. Current behavior, default.

OPEN

Agent can adapt identity, memory, tools, and triggers at runtime — a self-evolving, autonomous agent.

OpenModeConfig

promptise.runtime.config.OpenModeConfig

Bases: _WarnOnUnknownKeys

Guardrails for open execution mode.

Controls what the agent is allowed to self-modify and how far. Only takes effect when ProcessConfig.execution_mode is :attr:ExecutionMode.OPEN.

Security: open mode gives the model control over its own setup, so treat every allow_* capability as something the model (and whoever can put text in front of it: trigger payloads, inbox messages, tool results) can use. allow_tool_creation runs model-written Python inside the runtime's process; the restricted builtins are not a sandbox and can be escaped, so it amounts to running arbitrary code as the runtime's user. The control API refuses such deploys unless allow_command_deploys is on.

Attributes:

Name Type Description
allow_identity_change bool

Agent can modify its own instructions.

allow_tool_creation bool

Agent can define new Python tools, executed in the runtime's process (not isolated; see above).

allow_mcp_connect bool

Agent can connect to new MCP servers.

allow_trigger_management bool

Agent can add/remove triggers.

allow_memory_management bool

Agent can explicitly store/search/forget.

max_custom_tools int

Max number of agent-created tools.

max_dynamic_triggers int

Max number of dynamically added triggers.

max_instruction_length int

Max character length for modified instructions.

max_rebuilds int | None

Max agent rebuilds (hot reloads) per lifetime of the process object; restarts do not reset it. A change the limit refuses is not applied, now or after a restart. rollback() is not refused. None = unlimited.

allowed_mcp_urls list[str]

The MCP server URLs the agent may connect to. Empty means any public URL for an operator-configured process (loopback, private, link-local and cloud-metadata addresses are refused; a warning is logged at start) and none for a process deployed through the control API. Listed URLs are trusted as written, private addresses included.

identity_mcp_urls list[str]

Agent-connected MCP servers that may receive the process identity's credential (identity) and a caller's token. Empty (the default): none do, so the agent can't send its credential to a server it picked.

sandbox_custom_tools bool

Run agent-written tools with restricted builtins and no imports (False: a fixed allowlist of standard-library imports). Either way the code runs in the runtime's process; this is not a security boundary.

allow_process_spawn bool

Agent can spawn new processes within the runtime. Defaults to False.

max_spawned_processes int

Max number of processes this agent can spawn.

allow_public_webhooks bool

Webhook triggers the agent adds, or gives a process it spawns, may bind a non-loopback webhook_host (they must then set hmac_secret). False (default): such triggers are refused.

Unknown keys are ignored with a FutureWarning.

EscalationTarget

promptise.runtime.config.EscalationTarget

Bases: _WarnOnUnknownKeys

Where to send escalation notifications.

Attributes:

Name Type Description
webhook_url str | None

HTTP endpoint to POST a JSON payload to.

event_type str | None

EventBus event type to emit.

TriggerDeliveryConfig

promptise.runtime.config.TriggerDeliveryConfig

Bases: BaseModel

How a process handles trigger events that can't be processed.

Attributes:

Name Type Description
max_retries int

Extra attempts for an event whose agent run raised. 0 (default) never retries. A retry re-runs the whole agent turn, so tools with side effects may run twice — only enable it when those tools are idempotent or gated by approval.

retry_backoff float

Delay in seconds before the first retry; doubles with each further attempt.

retry_backoff_max float

Upper bound for the retry delay.

dead_letter_size int

How many undeliverable events to keep in :attr:AgentProcess.dead_letters (oldest dropped first).

scan_payloads bool

Run a prompt-injection scan on each trigger payload before the agent sees it. Flagged events are dead-lettered instead of run. Uses ProcessConfig.guardrails when it is a scanner, otherwise an injection-only :class:~promptise.guardrails.PromptiseSecurityScanner (needs transformers; the model loads when the process starts).

max_payload_chars int

Longest payload rendering placed in the prompt; longer payloads are truncated with a marker.

HookConfig

promptise.runtime.config.HookConfig

Bases: BaseModel

One runtime lifecycle hook of a process (ProcessConfig.hooks).

Either command (an external program run as a :class:~promptise.runtime.ShellHook; the form a manifest's hooks: section uses) or callback (an async Python callable taking a :class:~promptise.runtime.HookContext; code only). See :mod:promptise.runtime.hooks for the events, what each one's data holds and which ones can be blocked.

Attributes:

Name Type Description
event str

The :class:~promptise.runtime.HookEvent value, e.g. "user_prompt_submit".

command str | list[str] | None

Program and arguments: a list, or a string split like a shell would (no shell runs it). The event arrives on stdin as JSON.

callback Any | None

Async callable (code only; not serialised).

timeout float | None

Seconds the hook may run (default 5 for a command, 30 for a callback).

priority int

Higher runs first.

once bool

Run on the first matching event only.

tag str | None

Group name (:meth:HookManager.remove_by_tag).

fail_closed bool

An error or timeout of this hook blocks the action (for a blockable event) instead of being ignored.

cwd str | None

Working directory of the command.

env dict[str, SecretStr]

Extra environment variables for the command. Values may be ${VAR} references, resolved from the host environment when the hook runs; literal values are masked in JSON dumps.

env_passthrough list[str]

Host environment variables passed through to the command (it gets a minimal environment otherwise).

max_output_bytes int

Largest stdout accepted from the command.

Unknown keys are rejected.


Lifecycle

ProcessState

promptise.runtime.lifecycle.ProcessState

Bases: str, Enum

Agent process lifecycle states.

AWAITING is reserved: :class:~promptise.runtime.process.AgentProcess never enters it (see the module docstring).

ProcessLifecycle

promptise.runtime.lifecycle.ProcessLifecycle

State machine for process lifecycle.

Tracks the current state, validates transitions against :data:VALID_TRANSITIONS, and records an audit trail of all transitions. Transitions are serialised with an :class:asyncio.Lock, so concurrent tasks of one event loop can use it; it is not thread-safe.

Parameters:

Name Type Description Default
initial ProcessState

Starting state (default :attr:ProcessState.CREATED).

CREATED
process_id str | None

Owner name shown in :class:StateError messages.

None
lc = ProcessLifecycle()
assert lc.state == ProcessState.CREATED

await lc.transition(ProcessState.STARTING, reason="user requested")
await lc.transition(ProcessState.RUNNING)

assert len(lc.history) == 2
history property

Chronological list of all transitions.

state property

Current process state.

add_listener(listener)

Register a callback invoked after every successful transition.

Listeners run in registration order, outside the state lock, so they may inspect :attr:state (and even trigger further transitions). A listener that raises is logged and skipped; it never undoes the transition.

Parameters:

Name Type Description Default
listener TransitionListener

Sync or async callable receiving the :class:ProcessTransition.

required
can_transition(target)

Check whether transitioning to target is valid.

from_snapshot(data) classmethod

Reconstruct a :class:ProcessLifecycle from a snapshot dict.

snapshot()

Serializable snapshot of current state and full history.

transition(target, *, reason='', metadata=None) async

Attempt a state transition.

Parameters:

Name Type Description Default
target ProcessState

Desired next state.

required
reason str

Human-readable explanation.

''
metadata dict[str, Any] | None

Extra context to store with the transition.

None

Returns:

Type Description
ProcessTransition

The recorded :class:ProcessTransition.

Raises:

Type Description
StateError

If the transition is not valid from the current state.

ProcessTransition

promptise.runtime.lifecycle.ProcessTransition dataclass

Record of a single state transition.

Attributes:

Name Type Description
from_state ProcessState

Previous state.

to_state ProcessState

New state.

timestamp datetime

When the transition occurred (timezone.utc).

reason str

Human-readable reason for the transition.

metadata dict[str, Any]

Additional context (e.g. error details).

from_dict(data) classmethod

Deserialize from a dict.

to_dict()

Serialize to a JSON-compatible dict.

StateError

promptise.runtime.lifecycle.StateError

Bases: ProcessStateError, ValueError

Raised when an invalid state transition is attempted.

One exception type for invalid transitions: it is a :class:~promptise.runtime.exceptions.ProcessStateError (so except RuntimeBaseError catches it) and, as before, a :class:ValueError.

Attributes:

Name Type Description
current

The state the transition started from.

target

The rejected target state.

process_id

The owning process (its name), when known.

current_state

current.value (as on ProcessStateError).

attempted_state

target.value.


Triggers

TriggerConfig

promptise.runtime.config.TriggerConfig

Bases: BaseModel

Configuration for a single trigger.

Each trigger type requires a different subset of fields. The :meth:_validate_trigger_fields model validator enforces this for built-in types, and rejects bad cron expressions, time zones, filter expressions and source ranges when the config is created rather than when the process first runs. Custom trigger types use the custom_config dict. Unknown keys are rejected, so a misspelt option fails loudly instead of being ignored.

Attributes:

Name Type Description
type str

Trigger type (built-in or custom-registered via :func:~promptise.runtime.triggers.register_trigger_type).

cron_expression str | None

Cron expression (cron type only). Five fields, or six with a trailing seconds field ("* * * * * */10" fires every 10 seconds).

cron_timezone str | None

IANA time zone the cron expression is read in (cron only). None means UTC.

webhook_path str

URL path for the webhook endpoint (webhook only).

webhook_port int

Listening port (webhook only).

webhook_host str

Interface to bind (webhook only). Defaults to 127.0.0.1 (loopback); use 0.0.0.0 to accept requests from other machines.

hmac_secret SecretStr | None

Shared secret for request signatures (webhook only). When set, unsigned or wrongly signed requests get 401.

signature_scheme Literal['generic', 'github', 'stripe']

How the signature is computed and sent: generic (X-Webhook-Signature: sha256=<hex>), github (X-Hub-Signature-256: sha256=<hex>) or stripe (Stripe-Signature: t=<ts>,v1=<hex> over "<ts>.<body>").

signature_header str | None

Header carrying the signature. Defaults to the scheme's standard header.

signature_tolerance int

Max age in seconds of a timestamped (stripe) signature, to stop replays.

allowed_sources list[str]

Client IPs or CIDR ranges allowed to call the webhook (empty = any). Others get 403.

webhook_max_body_bytes int

Largest request body the webhook accepts, in bytes (webhook only, default 1 MiB). Larger bodies get 413.

watch_path str | None

Directory to watch (file_watch only).

watch_patterns list[str]

Glob patterns (file_watch only), matched against the file name.

watch_events list[str]

Filesystem events to react to (file_watch only): any of created, modified, deleted, moved.

watch_debounce_seconds float

Window in which events for the same file are merged into one (file_watch only).

watch_recursive bool

Also watch subdirectories (file_watch only, default True).

watch_backend Literal['auto', 'watchdog', 'polling']

How changes are detected (file_watch only): "auto" (native OS notifications through watchdog when it is installed, otherwise polling), "watchdog" (fail if it isn't installed) or "polling" (scan the directory every watch_poll_interval seconds; use it for network shares and bind mounts, where OS notifications are unreliable).

watch_poll_interval float

Seconds between scans with the polling backend (file_watch only).

watch_max_pending_events int

How many changed files the trigger holds while the process is busy (file_watch only). Events wait for the process instead of being dropped; changes to a file that is already waiting are merged into its event. Only when this many different files are waiting is a new one dropped, with an error log and a count in FileWatchTrigger.dropped_events.

event_type str | None

EventBus event type string (event only).

event_source str | None

Optional source filter (event only).

topic str | None

MessageBroker topic (message only).

custom_config dict[str, Any]

Additional key-value configuration for custom trigger types.

filter_expression str | Callable[..., Any] | None

Cheap pre-filter evaluated before the agent runs (all types, optional). Either a safe expression string such as "payload['action'] == 'opened'" (see :mod:promptise.runtime.triggers.filters) or a callable that takes the :class:~promptise.runtime.triggers.base.TriggerEvent and returns a bool. Events that don't match are skipped.

Setting an option that belongs to another built-in trigger type (for example watch_path on a cron trigger) is an error, since the trigger would ignore it.

effective_signature_header property

The header the webhook reads the signature from.

BaseTrigger

promptise.runtime.triggers.base.BaseTrigger

Bases: Protocol

Protocol for all trigger implementations.

Triggers produce :class:TriggerEvent objects that cause the agent process to invoke the agent. They run as background tasks and must be safely cancellable.

A trigger that buffers events may also implement drain_pending() -> list[TriggerEvent]: after :meth:stop, the process takes those events and dead-letters them (on a restart: queues them for the restarted process) instead of losing them.

start() async

Start listening for trigger conditions.

stop() async

Stop the trigger and release resources.

wait_for_next() async

Block until the next trigger event occurs.

Returns:

Type Description
TriggerEvent

The next :class:TriggerEvent.

Raises:

Type Description
CancelledError

If the trigger is stopped.

TriggerError

If the trigger encounters a fatal error.

TriggerEvent

promptise.runtime.triggers.base.TriggerEvent dataclass

Event produced by a trigger.

Attributes:

Name Type Description
trigger_id str

Which trigger produced this event.

trigger_type str

Type of the trigger (cron, webhook, etc.).

event_id str

Unique event identifier.

timestamp datetime

When the event was produced (timezone.utc).

payload dict[str, Any]

Trigger-specific data (cron time, webhook body, file path, event data, message content).

metadata dict[str, Any]

Additional context.

from_dict(data) classmethod

Deserialize from a dict.

to_dict()

Serialize to a JSON-compatible dict.

CronTrigger

promptise.runtime.triggers.cron.CronTrigger

Fires at scheduled intervals defined by a cron expression.

Parameters:

Name Type Description Default
cron_expression str

Cron expression (e.g. */5 * * * *), with an optional sixth seconds field.

required
trigger_id str | None

Unique identifier (auto-generated if not provided).

None
timezone str | None

IANA time zone the expression is read in (default UTC).

None

Raises:

Type Description
TriggerError

If the expression or time zone is invalid.

start() async

Mark the trigger as active.

stop() async

Mark the trigger as inactive and unblock waiters.

wait_for_next() async

Block until the next scheduled time.

Uses self._event to allow :meth:stop to unblock the wait immediately instead of sleeping for the full delay.

Returns:

Name Type Description
A TriggerEvent

Raises:

Type Description
CancelledError

If stopped while waiting.

TriggerError

If the cron expression is invalid.

WebhookTrigger

promptise.runtime.triggers.webhook.WebhookTrigger

HTTP webhook trigger.

Starts an aiohttp server that listens for incoming POST requests and converts them to :class:TriggerEvent objects.

Parameters:

Name Type Description Default
path str

URL path to listen on (e.g. "/webhook").

'/webhook'
port int

TCP port to bind to.

9090
host str

Host/IP to bind to. Defaults to "127.0.0.1" (loopback only). Set to "0.0.0.0" when external services or other nodes need to reach this webhook.

'127.0.0.1'
hmac_secret str | None

Optional HMAC secret for signature verification. When set, requests without a valid signature get 401. Verification uses timing-safe comparison. Strongly recommended when host is not loopback. Without it, a loopback webhook refuses browser-style cross-site requests (421 for a non-loopback Host, 403 for a cross-site Origin).

None
signature_scheme Literal['generic', 'github', 'stripe']

"generic", "github" or "stripe" (see the module docs).

'generic'
signature_header str | None

Header carrying the signature (defaults to the scheme's standard header).

None
signature_tolerance int

Max age in seconds of a stripe timestamp.

300
allowed_sources list[str] | None

Client IPs / CIDR ranges allowed to call the webhook. Empty means any. Checked against the TCP peer address, so behind a reverse proxy list the proxy.

None
event_filter Callable[[TriggerEvent], bool] | None

Optional predicate; events it rejects are answered with 200 {"status": "ignored"} and not queued.

None
max_body_bytes int

Largest request body accepted, in bytes (default 1 MiB). Larger requests get 413 and are not queued.

DEFAULT_MAX_BODY_BYTES

Raises:

Type Description
ValueError

Unknown signature_scheme or max_body_bytes < 1.

drain_pending()

Take the accepted (202) events nobody consumed (after :meth:stop).

set_availability_check(check)

Install a callback that says whether events can be accepted.

The callback returns None when events can be processed, or a short reason ("process failed", "queue full") when they can't; the webhook then answers 503. :class:AgentProcess installs this automatically.

start() async

Start the webhook HTTP server.

stop() async

Stop the webhook HTTP server.

wait_for_next() async

Wait for the next webhook event.

Returns:

Name Type Description
A TriggerEvent

Raises:

Type Description
CancelledError

If the wait is cancelled.

FileWatchTrigger

promptise.runtime.triggers.file_watch.FileWatchTrigger

Filesystem watch trigger.

Monitors a directory for file changes and produces :class:TriggerEvent objects.

Parameters:

Name Type Description Default
watch_path str

Directory to monitor.

required
patterns list[str] | None

Glob patterns to match (e.g. ["*.csv", "*.json"]).

None
events list[str] | None

Event types to react to (created, modified, deleted, moved). Defaults to created + modified.

None
recursive bool

Watch subdirectories recursively.

True
debounce_seconds float

Window in which all events for one path are merged into a single trigger event.

0.5
poll_interval float

Seconds between directory scans with the polling backend.

1.0
backend str

"auto" (watchdog when installed, else polling), "watchdog" or "polling".

'auto'
max_pending_events int

Most files that can wait for the consumer (queued plus backlog) before new changes are dropped.

10000

Raises:

Type Description
ValueError

An unknown event type or backend, backend="watchdog" without watchdog installed, or an out-of-range number.

backend property

The backend in use: "watchdog" or "polling".

dropped_events property

Changes dropped because max_pending_events files were waiting.

pending_events property

Events waiting for the consumer (queued plus backlog).

drain_pending()

Take the changes nobody consumed (after :meth:stop): the queued events, then the backlog and the changes still in their debounce window.

start() async

Start watching for file changes.

stop() async

Stop watching for file changes.

wait_for_next() async

Wait for the next file change event.

Returns:

Name Type Description
A TriggerEvent

Raises:

Type Description
CancelledError

If the wait is cancelled.

EventTrigger

promptise.runtime.triggers.event.EventTrigger

Trigger that fires when an EventBus event matches.

Wraps an EventBus subscription and converts matching events into :class:TriggerEvent objects via an internal queue.

Parameters:

Name Type Description Default
event_bus Any

An object with subscribe(event_type, handler) and unsubscribe(event_type, handler) methods.

required
event_type str

Event type string to listen for (e.g. "task.completed").

required
source_filter str | None

Optional source filter — only events from this source will fire the trigger.

None
trigger_id str | None

Unique identifier (auto-generated if not provided).

None
drain_pending()

Take the events nobody consumed (after :meth:stop).

start() async

Subscribe to the EventBus.

stop() async

Unsubscribe and drain the queue.

wait_for_next() async

Block until a matching event arrives.

Raises:

Type Description
CancelledError

If the trigger is stopped.

MessageTrigger

promptise.runtime.triggers.event.MessageTrigger

Trigger that fires when a message arrives on a broker topic.

Wraps a message broker subscription.

Parameters:

Name Type Description Default
broker Any

An object with async subscribe(topic, handler) (returning a subscription ID) and unsubscribe(subscription_id) methods.

required
topic str

Topic to subscribe to (supports wildcards like reports.*).

required
trigger_id str | None

Unique identifier (auto-generated if not provided).

None
drain_pending()

Take the messages nobody consumed (after :meth:stop).

start() async

Subscribe to the MessageBroker topic.

stop() async

Unsubscribe from the topic.

wait_for_next() async

Block until a message arrives on the topic.

Raises:

Type Description
CancelledError

If the trigger is stopped.

create_trigger

promptise.runtime.triggers.create_trigger(config, *, event_bus=None, broker=None)

Looks up the trigger type in the registry (built-in and custom types registered via :func:register_trigger_type).

Parameters:

Name Type Description Default
config TriggerConfig

Trigger configuration.

required
event_bus Any | None

Required for event type triggers.

None
broker Any | None

Required for message type triggers.

None

Returns:

Type Description
BaseTrigger

A trigger instance satisfying :class:BaseTrigger.

Raises:

Type Description
TriggerError

If the trigger type is unknown or dependencies are missing.

register_trigger_type

promptise.runtime.triggers.register_trigger_type(type_name, factory, *, overwrite=False)

Register a custom trigger type.

After registration, the type name can be used in :class:~promptise.runtime.config.TriggerConfig and :func:create_trigger.

Parameters:

Name Type Description Default
type_name str

The type string used in TriggerConfig.type.

required
factory TriggerFactory

Callable with signature (config, *, event_bus=None, broker=None) -> BaseTrigger.

required
overwrite bool

If True, allow overwriting an existing registration.

False

Raises:

Type Description
ValueError

If type_name is already registered and overwrite is False.

from promptise.runtime.triggers import register_trigger_type

def sqs_factory(config, *, event_bus=None, broker=None):
    return SQSTrigger(config.custom_config["queue_url"])

register_trigger_type("sqs", sqs_factory)

registered_trigger_types

promptise.runtime.triggers.registered_trigger_types()

Return the names of all registered trigger types.

Returns:

Type Description
list[str]

Sorted list of registered type names (built-in + custom).


Governance — Budget

BudgetConfig

promptise.runtime.config.BudgetConfig

Bases: _WarnOnUnknownKeys

Autonomy budget configuration (unknown keys warn and are ignored).

Attributes:

Name Type Description
enabled bool

Enable budget tracking for this process.

max_tool_calls_per_run int | None

Max tool calls per invocation.

max_llm_turns_per_run int | None

Max LLM turns per invocation.

max_cost_per_run float | None

Max cost units per invocation.

max_irreversible_per_run int | None

Max irreversible actions per invocation.

max_tool_calls_per_day int | None

Max tool calls in a day.

max_runs_per_day int | None

Max invocations in a day.

max_cost_per_day float | None

Max cost units in a day.

tool_costs dict[str, ToolCostAnnotation]

Per-tool cost annotations.

on_exceeded Literal['pause', 'stop', 'escalate']

Action on budget violation.

escalation EscalationTarget | None

Escalation target for violations.

daily_reset_hour_utc int

UTC hour to reset daily counters.

inject_remaining bool

Inject remaining budget into agent context.

warning_threshold float

Fraction of a limit at which a budget.warning event is emitted (default 0.8: 80%).

ToolCostAnnotation

promptise.runtime.config.ToolCostAnnotation

Bases: _WarnOnUnknownKeys

Cost and reversibility annotation for a tool.

Attributes:

Name Type Description
cost_weight float

Abstract cost units per invocation (default 1.0).

irreversible bool

Whether the tool performs an irreversible action.

BudgetState

promptise.runtime.budget.BudgetState

Thread-safe budget counters with per-run and daily tracking.

Parameters:

Name Type Description Default
config BudgetConfig

Budget configuration with limits and tool cost annotations.

required
budget_context()

Format remaining budget as a context string for injection.

check_daily_reset() async

Reset daily counters if the reset hour has passed.

The day runs from daily_reset_hour_utc to the same hour the next day: the counters reset when the last reset happened before the most recent such boundary. (In 1.4.3 and earlier they also reset at midnight UTC whatever the configured hour.)

Returns:

Type Description
bool

True if counters were reset, False otherwise.

from_dict(data, config) classmethod

Reconstruct from a journal checkpoint.

record_llm_turn() async

Record an LLM turn and check limits.

record_run_start() async

Record start of a new invocation and check daily run limit.

record_tool_call(tool_name, *, enforce=False) async

Record a tool call and check per-run + daily limits.

Parameters:

Name Type Description Default
tool_name str

The tool about to run.

required
enforce bool

Check the call before counting it. If running it would exceed a limit (or a limit is already exceeded, such as max_llm_turns_per_run), the call is not counted and the returned violation has blocked=True — the caller must not execute the tool.

False

Returns the first :class:BudgetViolation found, or None.

record_tool_call_sync(tool_name, *, enforce=False)

Synchronous :meth:record_tool_call (thread-safe).

Used by the budget guard callback, which LangChain may run on a worker thread for synchronous tools.

remaining()

Return remaining budget per category.

None means unlimited. Non-negative values indicate remaining capacity. Negative values mean the limit was exceeded.

reset_run() async

Reset per-run counters at the start of each invocation.

restore(data)

Load counters saved by :meth:snapshot into this state.

The daily counters and last_daily_reset are restored as recorded: if the daily reset hour has passed since, the next :meth:check_daily_reset resets them as usual. The per-run counters start at zero (a new run resets them anyway). The config (limits) stays this state's own.

Raises:

Type Description
ValueError

data is not a snapshot (a counter is negative, not a number, or last_daily_reset is not an ISO timestamp). Nothing is changed.

snapshot()

The counters as a JSON-serialisable dict (for a journal checkpoint).

:class:~promptise.runtime.AgentProcess stores it in every checkpoint as "budget" and passes it to :meth:restore on journal recovery, so restarting a process (or the whole runtime) doesn't reset its daily limits.

to_dict()

Serialise for journal checkpointing (same as :meth:snapshot).

BudgetEnforcer

promptise.runtime.budget.BudgetEnforcer

Handles budget violations according to the configured policy.

Parameters:

Name Type Description Default
config BudgetConfig

Budget configuration with on_exceeded policy.

required
handle_violation(violation, process) async

Execute the configured response to a budget violation.

Actions: - "pause": Suspend the process. - "stop": Stop the process entirely. - "escalate": Fire escalation notification, then suspend.

Parameters:

Name Type Description Default
violation BudgetViolation

The budget violation that occurred.

required
process Any

The owning AgentProcess.

required

BudgetViolation

promptise.runtime.budget.BudgetViolation dataclass

Describes a budget limit that was exceeded.

Attributes:

Name Type Description
limit_name str

Which limit was hit (e.g. "max_tool_calls_per_run").

limit_value float

The configured maximum.

current_value float

The counter value that exceeded the limit (for a blocked call: the value the call would have produced).

tool_name str | None

Tool that triggered the violation (if applicable).

blocked bool

True when the tool call was refused before it ran (per-call enforcement) rather than detected after the fact.

BudgetWarning

promptise.runtime.budget.BudgetWarning dataclass

Describes a budget limit approaching its threshold (default 80%).

Attributes:

Name Type Description
limit_name str

Which limit is approaching (e.g. "max_tool_calls_per_day").

limit_value int | float

The configured maximum.

current_value int | float

The current counter value.

percentage float

How close to the limit (0.0-1.0).


Governance — Health

HealthConfig

promptise.runtime.config.HealthConfig

Bases: _WarnOnUnknownKeys

Behavioral health monitoring configuration.

Attributes:

Name Type Description
enabled bool

Enable health monitoring for this process.

stuck_threshold int

Same tool+args N times in a row = stuck.

loop_window int

Tool calls to examine for loop detection.

loop_min_repeats int

Min pattern repeats to trigger.

empty_threshold int

N consecutive trivial final replies = anomaly.

empty_max_chars int

A final reply of at most this many characters (after stripping whitespace) is trivial. With the default 10, a 10-character reply counts as empty and an 11-character one doesn't; 0 counts only blank replies.

error_window int

Number of recent invocations the error rate is computed over. The rate is checked once the window is full.

error_rate_threshold float

An error rate at or above this (0–1) in a full window is an anomaly.

on_anomaly Literal['log', 'pause', 'escalate', 'stop']

What the process does when a run produced an anomaly (a health.anomaly event is emitted in every case): "log" logs it; "pause" suspends the process; "escalate" sends the escalation notification to escalation (when set) and suspends the process; "stop" stops the process.

cooldown float

Seconds before the same anomaly (same kind and same evidence: the same stuck call or loop) is reported again. Set 0 to report every repeat, e.g. for an in-run guard. :meth:HealthMonitor.clear_anomalies resets it.

escalation EscalationTarget | None

Escalation target for anomalies.

Window sizes can be changed on a running monitor (for example with PATCH /api/v1/processes/{name}/health); the monitor resizes its windows and keeps the recorded data. Unknown keys are ignored with a FutureWarning.

HealthMonitor

promptise.runtime.health.HealthMonitor

Lightweight behavioral health analysis.

All detection methods are pure logic — no I/O, no LLM calls. The same anomaly is reported at most once per cooldown seconds, to avoid spam. "The same" means the same kind and the same evidence: the same repeated call (stuck), the same repeating sequence (loop), or the same signal (empty responses, error rate). A different stuck call or loop is a new anomaly and is reported at once.

To use the monitor as an in-run guard that acts on every repeat (a pre_tool hook that blocks the call while the agent is stuck), set cooldown=0: :meth:record_tool_call then returns the anomaly on each call that continues it.

Window sizes follow the config: when error_window, loop_window, stuck_threshold or empty_threshold change on the config object the monitor reads (as PATCH /health does), the windows are resized on the next recorded call, keeping the most recent data. Call :meth:apply_config to switch to a different :class:HealthConfig object.

The anomaly history keeps the latest :attr:MAX_ANOMALIES anomalies; the per-type counts in :meth:health_status count all of them.

Parameters:

Name Type Description Default
config HealthConfig

Health monitoring configuration.

required
process_id str

Owning process ID.

required
anomalies property

Detected anomalies, oldest first (the latest :attr:MAX_ANOMALIES).

config property

The configuration the monitor reads.

latest_anomaly property

Most recent anomaly, or None.

tool_history property

Recent tool call history [(name, args_hash)].

apply_config(config)

Switch to config, resizing the windows and keeping the data.

The most recent tool calls, reply lengths and error flags are kept (as many as fit the new windows), as are the anomaly history and the cooldowns. The new thresholds apply from the next recorded call.

begin_invocation()

Start a new invocation: forget the previous run's tool calls.

Stuck and loop detection look for repetition within a run. A scheduled agent that calls the same tool once per run (e.g. a queue check every minute) is behaving normally, so tool history does not carry over between invocations. Response lengths and the error window do carry over: "three empty replies in a row" and "half the recent runs failed" are cross-run signals.

clear_anomalies()

Forget detected anomalies and reset their cooldowns.

After a clear, the next occurrence of any anomaly is reported at once (not only after cooldown), counts start from zero and the monitor reports healthy. The tool-call, reply-length and error windows are kept: they are evidence, not reports.

Returns:

Type Description
int

The number of anomalies that were in the history.

from_dict(data, config, *, process_id='recovered') classmethod

Reconstruct from a journal checkpoint.

health_status()

Health summary (public API; JSON-safe).

Returns:

Type Description
dict[str, Any]

is_healthy (no anomaly reported within the last

dict[str, Any]

cooldown seconds), anomaly_count (total reported since

dict[str, Any]

the start or the last :meth:clear_anomalies),

dict[str, Any]

anomalies_by_type and latest_anomaly (type, description,

dict[str, Any]

ISO timestamp) or None.

record_error() async

Record a failed invocation and check error rate.

record_response(content) async

Record the agent's final reply and check for empty responses.

Pass the reply the agent gave at the end of an invocation, not tool results: a tool returning [] is not an empty response. A reply of at most empty_max_chars characters (after stripping whitespace) counts as empty.

record_success() async

Record a successful invocation.

A run that itself produced an anomaly (a stuck or loop pattern during its tool calls) is not a recovery, even when it finished.

Returns:

Type Description
bool

True if this success clears a previous anomaly (recovery).

record_tool_call(tool_name, args) async

Record a tool call and check for stuck/loop anomalies.

take_new_anomalies()

Return the anomalies detected since the previous call.

Each anomaly is returned once. AgentProcess calls this when a run ends (successfully or not) to apply on_anomaly to what the run produced, however long ago in the run it was detected.

to_dict()

Serialise for journal checkpointing.

Anomaly

promptise.runtime.health.Anomaly dataclass

A detected behavioral anomaly.

Attributes:

Name Type Description
anomaly_type AnomalyType

Kind of anomaly.

description str

Human-readable explanation.

timestamp datetime

When the anomaly was detected.

details dict[str, Any]

Additional evidence (tool sequences, counts, etc.).

AnomalyType

promptise.runtime.health.AnomalyType

Bases: str, Enum

Types of behavioral anomaly.


Governance — Mission

MissionConfig

promptise.runtime.config.MissionConfig

Bases: _WarnOnUnknownKeys

Mission-oriented process configuration.

Attributes:

Name Type Description
enabled bool

Enable mission tracking for this process.

objective str

What the agent is trying to achieve.

success_criteria str

How to judge completion.

eval_model str | None

LLM for evaluation (None = use process model).

eval_every int

Evaluate every N invocations.

confidence_threshold float

An evaluation that isn't achieved and has a lower confidence escalates to escalation (when set); the process keeps running (it is not paused).

timeout_hours float

Max hours (0 = no timeout).

max_invocations int

Max invocations (0 = unlimited).

auto_complete bool

Stop the process when the mission is achieved (False keeps it running; its later invocations are skipped, since the mission is completed).

on_complete Literal['stop', 'continue', 'suspend'] | None

Deprecated and ignored. It was never read: what happens on completion is decided by auto_complete alone. Setting it emits a FutureWarning; it will be removed.

escalation EscalationTarget | None

Escalation target for low confidence.

success_check Callable[[Any], bool | None] | None

Optional programmatic check, called with the :class:~promptise.runtime.mission.MissionEvidence before the LLM judge: True (achieved) or False (not achieved) decides, None falls through to the judge. Python only (not in manifests or the API) and never serialised.

Unknown keys are ignored with a FutureWarning.

MissionTracker

promptise.runtime.mission.MissionTracker

Tracks mission progress across invocations.

Handles evaluation scheduling, LLM-as-judge calls, programmatic checks, timeout/limit checking, and context prompt generation.

completed and failed are final: a judge verdict that arrives after the mission was failed (for example by POST /mission/fail while an evaluation was running) doesn't change it. Only an active mission can be paused; only a paused one resumed.

The tracker keeps the latest :attr:MAX_EVALUATIONS evaluations. Its state round-trips through :meth:to_dict and :meth:restore (or :meth:from_dict), so a process can carry the mission across a restart: invocation count, elapsed time, state, failure reason and evaluation history.

Parameters:

Name Type Description Default
config MissionConfig

Mission configuration.

required
process_id str

Owning process ID.

required
journal JournalProvider | None

Optional journal provider for logging evaluations.

None
success_check Callable[[MissionEvidence], bool | None] | None

Optional callable (MissionEvidence) -> bool | None. Return True for achieved, False for not achieved, None for inconclusive (fall through to LLM evaluator).

None
evaluation_count property

Total evaluations performed (including ones no longer kept).

evaluations property

Evaluations performed so far, oldest first (the latest :attr:MAX_EVALUATIONS).

failure_reason property

Why the mission failed (None unless it is failed).

invocation_count property

Number of invocations since mission started.

state property

Current mission state.

complete()

Transition mission to COMPLETED state.

A failed mission stays failed.

context_summary()

Generate context for injection into the agent's system prompt.

evaluate(evidence, model) async

Run a mission evaluation.

If a success_check callable is configured, it runs first. If it returns True → mission achieved. False → not achieved. None → fall through to LLM evaluator.

If the LLM call fails, returns a safe default evaluation (achieved=False, confidence=0.5).

Parameters:

Name Type Description Default
evidence MissionEvidence

Evidence bundle with conversation, state, tool log.

required
model ModelLike

Fallback model (an ID such as "openai:gpt-5-mini" or a chat model instance) if eval_model is not set.

required

Returns:

Type Description
MissionEvaluation

The evaluation result.

evaluation_history(limit=10)

The latest limit evaluations, oldest first.

fail(reason, *, source='manual')

Transition mission to FAILED state and record why.

The reason is kept (:attr:failure_reason, :meth:to_dict, the [Mission] prompt block) and journalled as a mission_failed entry.

Parameters:

Name Type Description Default
reason str

Why the mission failed.

required
source str

Who failed it: "manual" (operator / API), "timeout", "max_invocations" or another short tag.

'manual'

Returns:

Type Description
dict[str, Any]

The payload for a mission.failed event: process_id,

dict[str, Any]

reason (the source tag, as the runtime's own

dict[str, Any]

mission.failed events use it), message (reason),

dict[str, Any]

objective, invocation_count, previous_state and

dict[str, Any]

failed_at (ISO UTC).

Raises:

Type Description
ValueError

The mission is already completed or failed (both are final; in 1.4.3 and earlier a completed mission could be failed afterwards).

from_dict(data, config, *, process_id='recovered', journal=None, success_check=None) classmethod

Reconstruct from a journal checkpoint (see :meth:restore).

history_summary(limit=10)

:meth:context_summary plus the latest limit evaluations.

What the check_mission meta-tool returns: one line per evaluation with its invocation number, source, verdict, confidence and reasoning (each reasoning cut at 200 characters).

increment_invocation()

Increment the invocation counter. Called by AgentProcess.

is_over_limit()

Check whether the invocation limit has been exceeded.

is_timed_out()

Check whether the mission has exceeded its timeout.

pause()

Transition an ACTIVE mission to PAUSED (awaiting human input).

Has no effect on a completed or failed mission.

restore(data)

Load a :meth:to_dict snapshot into this tracker.

Restores the state, invocation count, elapsed time (the timeout clock continues where it stopped), failure reason and evaluation history. Configuration, journal and success_check stay the tracker's own. A snapshot taken for a different objective is refused, since its progress doesn't apply.

Raises:

Type Description
ValueError

data is malformed or for another objective.

resume()

Transition mission from PAUSED back to ACTIVE.

should_evaluate()

Check whether it's time to run an evaluation.

Returns True if the mission is active and the invocation count is a multiple of eval_every.

to_dict()

Serialise for journal checkpointing (JSON-safe).

Restore it with :meth:restore (into the process's own tracker) or :meth:from_dict.

MissionEvidence

promptise.runtime.mission.MissionEvidence dataclass

Bundle of evidence passed to the evaluator.

Built automatically by AgentProcess._invoke_agent() from the conversation buffer, AgentContext state, health monitor tool history, and the trigger event.

Attributes:

Name Type Description
conversation list[dict[str, Any]]

Recent conversation messages.

state dict[str, Any]

AgentContext key-value state snapshot.

tool_calls list[tuple[str, str]]

Recent tool call log [(name, args_hash)].

trigger_event dict[str, Any]

The trigger event that caused this invocation.

invocation_count int

Total invocations so far.

MissionEvaluation

promptise.runtime.mission.MissionEvaluation dataclass

Result of an evaluation.

Attributes:

Name Type Description
achieved bool

Whether the mission is complete.

confidence float

Evaluator confidence (0.0–1.0).

reasoning str

Explanation of the evaluation.

progress_summary str

Brief summary of progress so far.

timestamp datetime

When the evaluation was performed.

invocation_number int

Which invocation triggered this evaluation.

source str

"llm" or "programmatic".

skipped bool

True when no evaluation ran (another invocation was evaluating at the same time, concurrency > 1). Such a result is not recorded; the process emits no mission event and doesn't escalate for it.

MissionState

promptise.runtime.mission.MissionState

Bases: str, Enum

Mission lifecycle states.


Governance — Secrets

SecretScopeConfig

promptise.runtime.config.SecretScopeConfig

Bases: _WarnOnUnknownKeys

Per-process secret scoping configuration.

Attributes:

Name Type Description
enabled bool

Enable secret scoping for this process.

secrets dict[str, SecretStr]

Secret name to value or environment reference. A reference (${VAR}, ${VAR:-default} or text containing them) is resolved from os.environ when the process starts, not when the config is built, so the config holds no secret value. Values are stored as :class:~pydantic.SecretStr: repr() shows ********** for every value; JSON dumps (model_dump(mode="json")) mask literal values and keep references as written, so a dumped config can be loaded again.

default_ttl float | None

Default TTL in seconds (None = no expiry).

ttls dict[str, float]

Per-secret TTL overrides in seconds. Every name must be a key of secrets.

revoke_on_stop bool

Zero-fill and remove all secrets on stop.

Unknown keys are ignored with a FutureWarning.

SecretScope

promptise.runtime.secrets.SecretScope

Per-process secret vault with TTL, access logging, and rotation.

Parameters:

Name Type Description Default
config SecretScopeConfig

Secret scoping configuration.

required
process_id str

Owning process ID (for journal entries).

required
journal JournalProvider | None

Optional journal provider for access logging.

None
source str

Where the config came from: "operator" (code or a manifest on the host) or "api" (a control-API deploy, or a process spawned by one). Only changes the error messages: for "api" a failed reference gives the same generic error whether the variable is refused or not set.

'operator'
env_references bool | Collection[str]

Which environment variables ${VAR} references may read: True (any, the default), False (none: a reference fails to resolve) or a collection of variable names.

True
active_secret_count property

Number of non-expired secrets currently stored.

active_secret_names property

Names of non-expired secrets currently stored.

active_secrets property

Names of non-expired secrets (alias of :attr:active_secret_names).

get(name)

Get a secret value by name.

Returns None if the secret doesn't exist or has expired. Every access is logged to the journal (name only, never value).

list_metadata()

Describe every configured secret — never its value.

Returns:

Type Description
list[dict[str, Any]]

One JSON-safe dict per secret in config.secrets:

list[dict[str, Any]]

name, active (present and not expired), source,

list[dict[str, Any]]

ttl (seconds the current value was given, None = no

list[dict[str, Any]]

expiry), expires_in (seconds left, None = no expiry or

list[dict[str, Any]]

not active), rotation_count, rotated_at (ISO UTC or

list[dict[str, Any]]

None) and access_count.

list_names()

Return names of all active (non-expired) secrets.

resolve_initial(*, missing_only=False) async

Resolve all secrets from config on startup.

Each value in config.secrets is either a literal string or contains environment references (${VAR}, ${VAR:-default}), which are resolved from os.environ now. Calling it again re-resolves every secret and resets its TTL.

Parameters:

Name Type Description Default
missing_only bool

Resolve only the secrets the scope doesn't hold (never resolved, revoked or expired) and keep the others, including values set by :meth:rotate. A process uses it when it starts again (revoke_on_stop=False), so an operator's rotation survives a restart.

False

Raises:

Type Description
SecretResolutionError

A referenced environment variable is not set and has no default, or env_references doesn't allow it (a :class:KeyError). The message names the variable and the secret, except for source="api".

revoke_all() async

Remove all secrets from the in-memory store.

Called on process stop. Overwrites values with null bytes as a best-effort measure (Python's immutable strings mean the original value may persist in the interpreter's memory pool until garbage-collected). Retired values are discarded too, so :meth:sanitize_text no longer knows any value afterwards.

rotate(name, new_value, *, ttl=None) async

Replace a secret's value immediately.

The old value is overwritten in memory (and kept, encrypted, only so that :meth:sanitize_text keeps redacting it). The rotation event is logged to the journal (name only).

Parameters:

Name Type Description Default
name str

A secret configured in config.secrets. An expired secret can be rotated: it becomes active again.

required
new_value str

The new value (non-empty).

required
ttl float | None

Seconds the new value is valid for, from now. None keeps the current expiry; for an expired secret, None applies its configured TTL (ttls / default_ttl) from now.

None

Raises:

Type Description
SecretNotFoundError

The name isn't a configured secret (a :class:KeyError).

ValueError

new_value is empty or ttl isn't positive.

sanitize(value)

:meth:sanitize_text applied to every string in a nested value.

Dicts (keys and values), lists and tuples are walked; other values are returned unchanged. Returns a new object, the input is not modified.

sanitize_text(text)

Replace secret values found in text with [REDACTED].

Redacts current values and values that were replaced by :meth:rotate or expired (the last :data:MAX_RETIRED_VALUES per secret), longest first. The runtime uses it to keep secret values out of the journal; call it on any text you log or show.

SecretNotFoundError

promptise.runtime.secrets.SecretNotFoundError

Bases: KeyError

No secret with this name is configured in the scope.

Subclasses :class:KeyError (what :meth:SecretScope.rotate raised in 1.4.3 and earlier).

Attributes:

Name Type Description
name

The requested secret name.


Inbox (Human-in-the-loop)

InboxConfig

promptise.runtime.config.InboxConfig

Bases: BaseModel

Configuration for the human-to-agent message inbox.

Attributes:

Name Type Description
enabled bool

Enable the message inbox.

max_messages int

Maximum messages in the inbox at once.

max_message_length int

Maximum characters per message.

default_ttl float

Default time-to-live in seconds (0 = no expiry).

max_ttl float

Maximum allowed TTL in seconds.

rate_limit_per_sender int

Maximum messages per sender per hour.

scan_with_guardrails bool

Scan incoming messages for prompt injection.

MessageInbox

promptise.runtime.inbox.MessageInbox

Async-safe inbox for human-to-agent messages.

Provides a bounded, priority-sorted queue with TTL-based expiry, per-sender rate limiting, and question/response tracking.

When the inbox holds max_messages messages, adding one removes an answered question if there is one, otherwise evicts the oldest message of the lowest priority present. Critical messages are never evicted, and a message is never added in place of one with a higher priority: if every message in the inbox is critical, or the new message has a lower priority than every evictable one, the new message is refused with :class:InboxFullError. The inbox never holds more than max_messages messages.

Parameters:

Name Type Description Default
max_messages int

Maximum messages in the inbox.

50
max_message_length int

Maximum characters per message; longer content is truncated.

2000
default_ttl float

Default time-to-live in seconds (0 = no expiry).

3600
max_ttl float

Maximum allowed TTL in seconds (0 = no cap).

86400
rate_limit_per_sender int

Maximum messages per sender per hour (messages without sender_id are not limited).

20
scanner Any | None

Optional guardrail scanner (an object with scan_text(text, direction=...) such as :class:~promptise.guardrails.PromptiseSecurityScanner, or with check_input(text)). When set, :meth:add refuses content it flags with :class:InboxMessageRejected.

None
add(message) async

Add a message to the inbox.

Content longer than max_message_length is truncated. The expiry is set from default_ttl when the message has none, and capped at max_ttl.

Parameters:

Name Type Description Default
message InboxMessage

The message to add.

required

Returns:

Type Description
str

The message_id.

Raises:

Type Description
InvalidInboxMessage

Empty content, unknown priority or type.

InboxMessageRejected

The scanner flagged the content.

InboxFullError

The inbox is full and nothing can be evicted.

InboxRateLimitError

The sender exceeded its hourly limit.

All four subclass
clear() async

Clear all messages.

Whoever waits for the answer to a cleared question gets :class:InboxQuestionRemoved (in 1.4.3 and earlier the wait was cancelled, which dropped an /ask request's connection).

get_pending() async

Get all non-expired pending messages, sorted by priority.

Returns messages in order: critical → high → normal → low, then by timestamp (oldest first within same priority). Questions that were already answered are left out. Does NOT remove messages — call :meth:remove_consumed after the agent has seen them.

get_questions() async

Get pending questions that haven't been answered yet.

mark_processed(message_id) async

Mark a message as processed (remove from inbox).

Questions are kept until their response is submitted.

purge_expired() async

Remove all expired messages. Returns count removed.

remove_consumed(delivered) async

Remove the messages a run consumed.

Call after a run with the messages that were placed in its prompt (the list from :meth:get_pending). Directives, context and corrections are removed: the agent has seen them. Questions are removed once answered (see :meth:submit_response); unanswered questions stay until they are answered or expire.

Returns:

Type Description
int

How many messages were removed.

rescan() async

Scan the messages already held with :attr:scanner; remove flagged ones.

For a scanner attached after messages arrived (a process attaches its payload scanner when it starts, and a created process already takes messages). A removed question's waiter gets :class:InboxMessageRejected.

Returns:

Type Description
list[tuple[str, str]]

(message_id, reason) of every removed message (empty

list[tuple[str, str]]

without a scanner).

status() async

Get inbox status summary (answered questions are not pending).

submit_response(question_id, response) async

Submit an agent's response to a question.

Resolves the future that the caller of :meth:wait_for_response is awaiting.

Parameters:

Name Type Description Default
question_id str

The message_id of the original question.

required
response InboxResponse

The agent's response.

required

Raises:

Type Description
KeyError

If no pending question with this ID.

wait_for_response(question_id, timeout=300) async

Wait for the agent to respond to a question.

Parameters:

Name Type Description Default
question_id str

The message_id of the question.

required
timeout float

Maximum seconds to wait.

300

Returns:

Type Description
InboxResponse

The agent's response.

Raises:

Type Description
KeyError

If no pending question with this ID.

TimeoutError

If timeout expires, or the question expired.

InboxQuestionRemoved

The inbox was cleared.

InboxFullError

The question was evicted from a full inbox.

InboxMessageRejected

A later scan removed it (:meth:rescan).

InboxMessage

promptise.runtime.inbox.InboxMessage dataclass

A message from a human to a running agent.

Attributes:

Name Type Description
message_id str

Unique cryptographic ID.

content str

The message text.

message_type MessageType

Type of message (directive, context, question, correction).

sender_id str | None

Who sent it (for audit trail).

priority str

Message priority (low, normal, high, critical).

created_at float

When the message was created.

expires_at float | None

When the message becomes stale (None = never).

metadata dict[str, Any]

Developer-provided custom data.

is_expired()

Check if the message has expired.

to_dict()

Serialize to a JSON-safe dict.

InboxResponse

promptise.runtime.inbox.InboxResponse dataclass

An agent's response to a question.

Attributes:

Name Type Description
question_id str

The message_id of the original question.

content str

The agent's answer text.

answered_at float

When the agent generated the answer.

invocation_id str | None

Which invocation produced the answer.

to_dict()

Serialize to a JSON-safe dict.

MessageType

promptise.runtime.inbox.MessageType

Bases: str, Enum

Types of human-to-agent messages.

InboxError

promptise.runtime.inbox.InboxError

Bases: ValueError

Base class for messages the inbox refuses.

InvalidInboxMessage

promptise.runtime.inbox.InvalidInboxMessage

Bases: InboxError

The message is malformed (empty content, unknown priority or type).

InboxRateLimitError

promptise.runtime.inbox.InboxRateLimitError

Bases: InboxError

The sender exceeded rate_limit_per_sender messages per hour.

InboxFullError

promptise.runtime.inbox.InboxFullError

Bases: InboxError

The inbox is at max_messages and nothing can be evicted for this message.

InboxMessageRejected

promptise.runtime.inbox.InboxMessageRejected

Bases: InboxError

The message's content was flagged by the guardrail scanner.

Attributes:

Name Type Description
reason

What the scanner found (finding categories).


Journal

JournalConfig

promptise.runtime.config.JournalConfig

Bases: _WarnOnUnknownKeys

Journal (durable audit log) configuration.

Attributes:

Name Type Description
level Literal['none', 'checkpoint', 'full']

Detail level — none (disabled), checkpoint (state snapshots per cycle), or full (every side effect).

backend Literal['file', 'memory']

Storage backend.

path str

Directory for the file backend.

recover_on_start bool

On a process object's first :meth:start, when the journal holds a checkpoint for the process name, restore what it recorded: the context state (with the context_update entries written after the checkpoint), the mission, the budget counters (so a restart can't reset the daily limits), the conversation history and the invocation count. Recovered keys replace the same keys of context.initial_state. A clean :meth:stop writes a checkpoint too, so the next run continues from it. True by default (a journal is opt-in, and recovery is what it is for); False always starts fresh.

JournalProvider

promptise.runtime.journal.JournalProvider

Bases: Protocol

Protocol for journal backends.

append(entry) async

Append an entry to the journal.

checkpoint(process_id, state) async

Store a full state checkpoint.

close() async

Release any resources.

last_checkpoint(process_id) async

Return the most recent checkpoint state, or None.

read(process_id, *, since=None, entry_type=None, limit=None) async

Read entries for a process with optional filters.

JournalEntry

promptise.runtime.journal.JournalEntry dataclass

Single journal record.

Attributes:

Name Type Description
entry_id str

Unique entry ID (a rewind target). An empty ID is replaced by a new one, so every entry has a real ID.

process_id str

Owning process.

timestamp datetime

When the entry was recorded (timezone.utc).

entry_type str

Type of entry (state_transition, trigger_event, invocation_start, invocation_result, checkpoint, error).

data dict[str, Any]

Entry payload.

from_dict(data) classmethod

Deserialize from a dict.

An entry stored without an ID (1.4.3 and earlier wrote some with "") gets one derived from its content, the same on every read, so it can still be a rewind target.

to_dict()

Serialize to a JSON-compatible dict.

JournalLevel

promptise.runtime.journal.JournalLevel

Bases: str, Enum

Journal detail level.

FileJournal

promptise.runtime.journal.FileJournal

JSONL-based journal for durable process audit logging.

One JSONL file per process, append-only writes. Checkpoints are stored separately in a JSON file.

Parameters:

Name Type Description Default
base_path str

Directory for journal files (created if missing).

'.promptise/journal'
journal = FileJournal("/tmp/journals")
await journal.append(JournalEntry(
    process_id="p-1",
    entry_type="trigger_event",
    data={"trigger_type": "cron"},
))
entries = await journal.read("p-1")
append(entry) async

Append an entry to the process's JSONL file.

checkpoint(process_id, state) async

Store a checkpoint and record it in the journal.

The checkpoint file is replaced atomically (written to a temporary file, then renamed), so a crash mid-write leaves the previous checkpoint intact instead of a truncated file.

close() async

No-op — file handles are opened/closed per operation.

last_checkpoint(process_id) async

Return the latest checkpoint from the checkpoint file.

read(process_id, *, since=None, entry_type=None, limit=None) async

Read entries from the process's JSONL file.

InMemoryJournal

promptise.runtime.journal.InMemoryJournal

In-memory journal for testing and development.

All entries are stored in a Python list — nothing is persisted.

Example::

journal = InMemoryJournal()
await journal.append(JournalEntry(process_id="p-1", entry_type="test"))
entries = await journal.read("p-1")
assert len(entries) == 1
append(entry) async

Append an entry.

checkpoint(process_id, state) async

Store a checkpoint.

close() async

No-op for in-memory backend.

last_checkpoint(process_id) async

Return the latest checkpoint.

read(process_id, *, since=None, entry_type=None, limit=None) async

Read entries with optional filters.

ReplayEngine

promptise.runtime.journal.ReplayEngine

Replays journal entries to reconstruct process state.

Parameters:

Name Type Description Default
journal JournalProvider

:class:JournalProvider to read from.

required
engine = ReplayEngine(journal)
recovered = await engine.recover("process-1")
# recovered = {
#     "context_state": {...},
#     "lifecycle_state": "running",
#     "last_entry_type": "invocation_result",
#     "entries_replayed": 3,
#     "mission": {...} or None,      # MissionTracker.to_dict()
#     "checkpoint": {...},           # the raw checkpoint (or {})
# }
recover(process_id) async

Recover process state from journal.

  1. Load the last checkpoint (if any).
  2. Read all entries after the checkpoint.
  3. Replay state transitions and context mutations.

Parameters:

Name Type Description Default
process_id str

Process to recover.

required

Returns:

Name Type Description
dict[str, Any]

Dict with context_state, lifecycle_state,

dict[str, Any]

last_entry_type, entries_replayed, mission (the

checkpointed dict[str, Any]
dict[str, Any]
dict[str, Any]

checkpoint (the raw last checkpoint, {} if none).

RedactingJournal

promptise.runtime.journal.RedactingJournal

A :class:JournalProvider that redacts before it writes.

Parameters:

Name Type Description Default
inner JournalProvider

The journal that stores the entries.

required
redact Callable[[Any], Any]

Callable that returns a redacted copy of a (nested) value, such as :meth:SecretScope.sanitize. It must not modify its input.

required
inner property

The wrapped journal.

append(entry) async

Append a redacted copy of entry.

checkpoint(process_id, state) async

Store a redacted copy of state.

close() async

Close the wrapped journal.

last_checkpoint(process_id) async

Return the latest checkpoint of the wrapped journal.

read(process_id, *, since=None, entry_type=None, limit=None) async

Read entries from the wrapped journal.

RewindEngine

promptise.runtime.journal.RewindEngine

Multi-granularity rewind over a journal.

Build one of these per process or share a single instance across processes — it's stateless besides holding a journal reference.

Parameters:

Name Type Description Default
journal JournalProvider

The :class:JournalProvider to read and write rewind markers to. The same journal used by the process.

required
apply(*, process_id, target_entry_id, mode, actor='system', record=True) async

Execute a rewind and return the new process state.

The journal is never edited or deleted by this method. The original entries remain. A new rewind entry is appended recording the operation (unless record=False).

Parameters:

Name Type Description Default
process_id str

Which process to rewind.

required
target_entry_id str

Entry to roll back to. The state is rebuilt by replaying entries up to but not including this one (according to mode).

required
mode RewindMode

Selectivity of the rewind. See :class:RewindMode.

required
actor str

Who initiated the rewind — recorded in the rewind entry. Defaults to "system".

'system'
record bool

If True (default), append a journal entry recording the rewind. Set False for tests / dry-runs that should leave no trace.

True

Returns:

Name Type Description
A RewindResult
RewindResult

lifecycle state, conversation, and an optional summary

RewindResult

note injected (for SUMMARIZE mode).

plan(*, process_id, target_entry_id, mode=RewindMode.CANCEL) async

Compute what would happen for a given rewind, without applying.

This is the preferred way to surface a rewind to a user before they confirm: build the plan, show entries_affected, then invoke :meth:apply once they pick a mode.

Parameters:

Name Type Description Default
process_id str

Which process to inspect.

required
target_entry_id str

Entry ID to roll back to. State will be rebuilt as of just before this entry.

required
mode RewindMode

Mode echoed back into the plan; defaults to CANCEL.

CANCEL

Returns:

Name Type Description
A RewindPlan

Raises:

Type Description
ValueError

If target_entry_id is not present in the journal for process_id.

RewindMode

promptise.runtime.journal.RewindMode

Bases: str, Enum

Selectivity of a :meth:RewindEngine.apply operation.

RewindPlan

promptise.runtime.journal.RewindPlan dataclass

Result of :meth:RewindEngine.plan — preview without committing.

Attributes:

Name Type Description
process_id str

Process being rewound.

target_entry_id str

Journal entry the rewind targets (state will be reconstructed as of just before this entry).

mode RewindMode

Selected mode (echoed for clarity).

entries_total int

Number of entries in the full journal.

entries_affected int

Number of entries that would be ignored by the rewind (those after the target).

conversation_entries_affected int

Of those, how many are conversation-style.

code_entries_affected int

Of those, how many are code/state-style.

target_timestamp datetime | None

When the target entry was written.

summary str

Human-readable summary of the rewind plan.

target_entry_type str

The target entry's type.

to_dict()

JSON-serialisable form (for the orchestration API).

RewindResult

promptise.runtime.journal.RewindResult dataclass

State produced by :meth:RewindEngine.apply.

context_state and conversation are the state after the rewind (per mode: rewound, or the current one kept). For CANCEL they are empty and only plan is set.


Manifests

AgentManifestSchema

promptise.runtime.manifest.AgentManifestSchema

Bases: BaseModel

Root schema for .agent manifest files.

Attributes:

Name Type Description
version Literal['1.0']

Schema version (currently "1.0").

name str

Process name (unique identifier).

model str

LLM model ID.

instructions str | None

System prompt.

servers dict[str, Any]

MCP server specifications.

triggers list[dict[str, Any]]

Trigger configurations (list of dicts).

world dict[str, Any]

Initial world state (key-value).

memory dict[str, Any] | None

Memory provider configuration.

journal dict[str, Any] | None

Journal configuration.

config dict[str, Any]

Additional process config overrides.

hooks list[dict[str, Any]]

Lifecycle hooks, each an external command run on an event (see :class:~promptise.runtime.config.HookConfig)::

hooks:
  - event: user_prompt_submit
    command: ["./hooks/audit.sh"]
    timeout: 5
    fail_closed: true
entrypoint str | None

Reserved; not read by the runtime. Python hooks are registered in code (process.hooks or HookConfig( callback=...)).

load_manifest

promptise.runtime.manifest.load_manifest(path)

Load and validate a .agent manifest file.

Parameters:

Name Type Description Default
path str | Path

Path to the .agent YAML file.

required

Returns:

Name Type Description
Validated AgentManifestSchema

Raises:

Type Description
ManifestError

If the file cannot be read or parsed.

ManifestValidationError

If schema validation fails.

save_manifest

promptise.runtime.manifest.save_manifest(manifest, path)

Write a manifest to a YAML file.

Parameters:

Name Type Description Default
manifest AgentManifestSchema

The manifest to save.

required
path str | Path

Destination file path.

required

validate_manifest

promptise.runtime.manifest.validate_manifest(path)

Validate a manifest file, returning a list of warnings.

Parameters:

Name Type Description Default
path str | Path

Path to the manifest file.

required

Returns:

Type Description
list[str]

List of warning messages (empty if fully valid).

Raises:

Type Description
ManifestError

If the file cannot be loaded.

ManifestValidationError

If the schema is invalid.

manifest_to_process_config

promptise.runtime.manifest.manifest_to_process_config(manifest)

Convert a manifest into a :class:ProcessConfig.

Parameters:

Name Type Description Default
manifest AgentManifestSchema

Validated manifest schema.

required

Returns:

Name Type Description
A ProcessConfig

Every section is passed on whole: each context: key sets the ContextConfig field of the same name, world: sets context.initial_state and memory: sets the memory_* fields. Unknown keys were rejected when the manifest was validated.


Orchestration API

Remote HTTP control of running processes.

OrchestrationAPI

promptise.runtime.api.OrchestrationAPI

REST API server for managing an AgentRuntime.

Parameters:

Name Type Description Default
runtime Any

The :class:AgentRuntime to manage.

required
host str

Bind address (default 127.0.0.1).

'127.0.0.1'
port int

Port to listen on (default 9100).

9100
auth_token str | None

Bearer token required on every route except GET /api/v1/health. Required when host is not a loopback address. "${VAR}" reads it from the environment.

None
on_shutdown Callable[[], Any] | None

Enables POST /api/v1/runtime/shutdown (authenticated like every route): the request is answered 202 {"status": "shutting_down"} and on_shutdown (a sync or async callable without arguments) is called right after, e.g. runtime.request_shutdown. Without it the route doesn't exist (404). promptise runtime start uses it for promptise runtime stop.

None
allow_command_deploys bool

Let POST /api/v1/processes deploy processes that run programs on this host: stdio MCP servers (command) and command hooks. Off by default, because anyone with the token could then run any command as the runtime's user; such a deploy answers 403 COMMANDS_NOT_ALLOWED. Turn it on only when every token holder may run commands here. Processes already in the runtime (loaded from manifests by the operator) are not checked. Open mode with open_mode.allow_tool_creation counts as running commands (the agent's create_tool is not sandboxed).

False
allow_env_references bool

Let deployed configs use ${VAR} references to any environment variable of this host (in secrets, which the agent can read back). Off by default: such a config answers 403 ENV_REFERENCES_NOT_ALLOWED.

False
allowed_env_vars Iterable[str] | None

The variables deployed configs may reference when allow_env_references is off, e.g. {"SEARCH_API_KEY"}.

None
data_dir str | Path | None

The directory deployed configs' file system paths must lie in: journal.path (file journal), context.memory_persist_directory and file-watch watch_path (also those the deployed agent adds later). Relative paths are resolved against the working directory, as the process uses them. Default: ./.promptise (where the journal goes by default). Others answer 403 PATH_NOT_ALLOWED.

None

Raises:

Type Description
ValueError

auth_token is empty, names an unset environment variable, or host is not a loopback address and there is no auth_token.

port property

The port the API listens on.

With port=0 the OS picks a free port; after :meth:start this returns the port actually bound.

start() async

Start the API server.

stop() async

Stop the API server.

OrchestrationClient

promptise.runtime.api_client.OrchestrationClient

Async client for the Promptise Orchestration API.

Provides typed methods for every API endpoint. Uses httpx internally — the client is an async context manager that manages the HTTP connection pool.

Parameters:

Name Type Description Default
base_url str

Base URL of the Orchestration API (e.g. "http://localhost:9100").

required
token str | None

Bearer token for authentication (the server's auth_token).

None
auth_token str | None

Alias of token, matching the server's argument name.

None
timeout float

Default request timeout in seconds.

30.0

Raises:

Type Description
ValueError

token and auth_token are both given and differ.

Use it as an async context manager, or call :meth:aclose when done (the connection pool is opened on first use).

Example::

async with OrchestrationClient("http://localhost:9100", token="tok") as c:
    await c.start_process("monitor")
    status = await c.get_process("monitor")
aclose() async

Close the HTTP connection pool.

add_trigger(name, trigger_config) async

Add a trigger to a process.

Returns:

Type Description
dict[str, Any]

trigger_id (use it with :meth:remove_trigger), live

dict[str, Any]

(whether it is already listening) and effective_from

dict[str, Any]

("immediate", "next_start" or "restart").

await client.add_trigger("monitor", {
    "type": "cron",
    "cron_expression": "0 9 * * 1-5",
})
ask(name, question, *, timeout=120, sender_id=None, priority='normal') async

Ask a question and wait for the agent's answer.

Long-polls until the agent answers or timeout expires. The agent sees the question on its next run, so an agent that doesn't run within timeout (no trigger fires) can't answer it.

Parameters:

Name Type Description Default
name str

Process name.

required
question str

Question text.

required
timeout float

Max seconds the server waits for the answer.

120
sender_id str | None

Identifier for the sender.

None
priority str

One of "low", "normal", "high", "critical".

'normal'

Returns:

Type Description
str | None

The agent's answer text, or None when it did not answer

str | None

within timeout (the server answers status: "timeout").

Raises:

Type Description
OrchestrationAPIError

For an error answer, e.g. 409 PROCESS_NOT_RUNNING or 400 INBOX_DISABLED.

clear_anomalies(name) async

Clear anomaly history (and cooldowns) after resolving the issue.

clear_inbox(name) async

Clear all messages in the inbox.

Returns:

Type Description
dict[str, Any]

{} (the server answers 204 No Content).

deploy(name, config, *, start=False) async

Deploy a new agent process.

Parameters:

Name Type Description Default
name str

Process name (unique).

required
config dict[str, Any]

ProcessConfig as a dict.

required
start bool

Whether to start immediately after deployment.

False
fail_mission(name, reason='Manually failed via client') async

Manually fail an active or paused mission (409 otherwise).

get_anomalies(name) async

Get anomaly history for a process.

get_context(name) async

Get the process's AgentContext state.

get_inbox(name) async

Get inbox status for a process.

get_journal(name, *, limit=50, entry_type=None) async

Read journal entries for a process.

Parameters:

Name Type Description Default
name str

Process name.

required
limit int

Max entries to return (1-500).

50
entry_type str | None

Filter by entry type (e.g. "state_transition").

None
get_metrics(name) async

Get process metrics (invocation count, budget usage, etc.).

get_mission(name) async

Get mission state and evaluation history.

get_process(name) async

Get detailed status for a single process.

health() async

Health check (no auth required).

list_processes() async

List all registered processes.

list_secrets(name) async

List secret metadata: name, source and expiry (never values).

list_triggers(name) async

List all triggers (from config and agent-added) for a process.

Each has a stable id: removing one trigger does not change the others' IDs.

pause_mission(name) async

Pause an active mission's evaluation (409 otherwise).

remove_process(name) async

Remove a process (stops it first if running).

Returns:

Type Description
dict[str, Any]

{} (the server answers 204 No Content).

remove_trigger(name, trigger_id) async

Remove a trigger by the id from :meth:list_triggers.

The response's live says whether the running trigger stopped now; when it is False the trigger keeps firing until the process restarts (effective_from: "restart").

restart_process(name) async

Restart a process (stop then start).

resume_mission(name) async

Resume a paused mission (409 otherwise).

resume_process(name) async

Resume a suspended process.

revoke_secrets(name) async

Revoke all secrets (zero-fill, immediate).

rewind(name, target_entry_id, *, mode='both', actor=None) async

Rewind a process to just before a journal entry.

See :meth:AgentProcess.rewind <promptise.runtime.AgentProcess.rewind>. Raises :class:OrchestrationAPIError with code ENTRY_NOT_FOUND, JOURNAL_DISABLED or REWIND_BUSY.

rewind_plan(name, target_entry_id, *, mode='both') async

Preview a rewind (changes nothing).

Parameters:

Name Type Description Default
name str

Process name.

required
target_entry_id str

A journal entry_id (see :meth:get_journal).

required
mode str

both, conversation_only, code_only, summarize or cancel.

'both'

Returns:

Type Description
dict[str, Any]

The plan: entries_affected, conversation_entries_affected,

dict[str, Any]

code_entries_affected, target_timestamp, summary, ...

rotate_secret(name, secret_name, value, *, ttl=None) async

Rotate a secret value (immediate effect).

Parameters:

Name Type Description Default
name str

Process name.

required
secret_name str

Name of the secret to rotate.

required
value str

New secret value.

required
ttl int | None

New lifetime in seconds, counted from now. Omitted: the secret keeps its current expiry.

None

Raises:

Type Description
OrchestrationAPIError

404 SECRET_NOT_FOUND when the process has no secret of that name.

runtime_status() async

Get global runtime status.

send_message(name, content, *, message_type='context', priority='normal', sender_id=None, ttl=None, metadata=None) async

Send a message to a running agent.

Parameters:

Name Type Description Default
name str

Process name.

required
content str

Message content.

required
message_type str

One of "directive", "context", "question", "correction".

'context'
priority str

One of "low", "normal", "high", "critical".

'normal'
sender_id str | None

Identifier for the sender (for audit trail).

None
ttl float | None

Time-to-live in seconds (message expires after TTL).

None
metadata dict[str, Any] | None

Custom data stored with the message.

None

Raises:

Type Description
OrchestrationAPIError

409 PROCESS_NOT_RUNNING when the process is stopped or failed, 429 RATE_LIMITED when the sender's rate limit is exceeded.

shutdown() async

Ask the server to shut its runtime down (POST /api/v1/runtime/shutdown).

Answered 202 {"status": "shutting_down"} when the API was created with on_shutdown (as promptise runtime start does); otherwise the route doesn't exist and this raises :class:OrchestrationAPIError with status 404.

start_all() async

Start all processes.

start_process(name) async

Start a process.

stop_all() async

Stop all processes.

stop_process(name) async

Stop a process.

suspend_process(name) async

Suspend a process (pause trigger processing).

update_budget(name, **fields) async

Update budget limits.

New limits are read at the next budget check (the next tool call, LLM turn or run start); usage counted so far is kept. The response's effective_from says when the change applies and live whether the running process uses it (False when it only applies after a redeploy, e.g. enabled on a process that was built without a budget).

Example::

await client.update_budget("monitor", max_cost_per_day=500.0)
update_context(name, state) async

Update AgentContext key-value state, all keys or none.

Only writable keys are accepted: if any key is not writable the server answers 403 WRITE_DENIED naming them, and writes nothing.

Parameters:

Name Type Description Default
name str

Process name.

required
state dict[str, Any]

Dict of key-value pairs to update.

required
update_health(name, **fields) async

Update health monitoring thresholds.

See the response's effective_from and live fields, as for :meth:update_budget.

update_instructions(name, instructions) async

Update the system prompt (effective next invocation).

update_mission(name, **fields) async

Update mission configuration.

See the response's effective_from and live fields, as for :meth:update_budget.

OrchestrationAPIError

promptise.runtime.api_client.OrchestrationAPIError

Bases: HTTPStatusError

An error answer from the Orchestration API.

Subclasses httpx.HTTPStatusError, so code that catches that keeps working.

Attributes:

Name Type Description
status_code

HTTP status (404, 409, ...).

code

The API's error code ("PROCESS_NOT_FOUND", ...), or None when the body was not an API error object.

error_message

The API's error message (or the raw body).

error_id

The id under which the server logged the failure's details (500 and other failures whose cause stays server-side), or None.


Distributed coordination

ClusterNode

promptise.runtime.distributed.ClusterNode

This runtime's membership in a cluster (see the module docstring).

Parameters:

Name Type Description Default
runtime Any

The :class:AgentRuntime the transport manages.

required
config DistributedConfig

Its distributed settings (enabled is not checked here).

required
role property

"coordinator", "node" (registers with one) or "standalone".

prune() async

Unregister nodes whose registration expired; returns their IDs.

start() async

Start the transport, the coordinator and/or the registration.

Raises:

Type Description
ValueError

The token's ${ENV_VAR} is not set.

OSError

The transport port can't be bound.

status()

JSON-serialisable summary (part of :meth:AgentRuntime.status).

stop() async

Unregister (best effort), stop the coordinator and the transport.

RuntimeTransport

promptise.runtime.distributed.RuntimeTransport

HTTP management API for an :class:AgentRuntime.

Exposes REST endpoints for remote process management:

  • GET /health — health check
  • GET /status — full runtime status
  • GET /processes — list all processes
  • GET /processes/{name}/status — single process status
  • POST /processes/{name}/start — start a process
  • POST /processes/{name}/stop — stop a process
  • POST /processes/{name}/restart — restart a process
  • POST /processes/{name}/event — inject a trigger event

With a coordinator (the node hosting the cluster coordinator):

  • GET /cluster/nodes — the registered nodes and their health
  • POST /cluster/nodes — register (or re-register) a node: {"node_id": ..., "url": ..., "metadata": {...}}; answers 409 with static discovery (the coordinator lists the nodes itself)
  • DELETE /cluster/nodes/{node_id} — unregister a node
  • GET /cluster/status — :meth:RuntimeCoordinator.cluster_status

Parameters:

Name Type Description Default
runtime Any

The :class:AgentRuntime to manage.

required
host str

Host to bind to (default 127.0.0.1 — localhost only).

'127.0.0.1'
port int

Port to bind to.

9100
node_id str

Unique identifier for this node.

'node-1'
auth_token str | None

Bearer token required on every endpoint except GET /health: requests must include Authorization: Bearer <token>. Required for a non-loopback host.

None
allow_unauthenticated bool

Allow a non-loopback host without auth_token. Only for nodes behind something that authenticates callers itself (an authenticating proxy, a private network you control); anyone who can reach the port can start, stop and drive your agents.

False
coordinator Any | None

A :class:RuntimeCoordinator to expose the /cluster routes for (this node hosts the coordinator).

None
discovery Any | None

The coordinator's discovery: a :class:RegistryDiscovery (nodes register through POST /cluster/nodes) or a :class:StaticDiscovery.

None
allowed_node_hosts list[str] | None

Hosts a registering node's URL may name (*.example.com for subdomains); others get 403. Empty or None: any host.

None

Raises:

Type Description
ValueError

auth_token is empty, or host is not a loopback address and there is no auth_token (and allow_unauthenticated is not set).

node_id property writable

This node's unique identifier.

port property

The port this transport is listening on.

With port=0 the OS picks a free port; after :meth:start this returns the port actually bound.

start() async

Start the HTTP transport server.

stop() async

Stop the HTTP transport server.

RuntimeCoordinator

promptise.runtime.distributed.RuntimeCoordinator

Cluster coordinator for distributed runtime management.

Tracks nodes, monitors health, and aggregates cluster state.

Parameters:

Name Type Description Default
health_check_interval float

Seconds between health checks.

15.0
node_timeout float

Seconds a healthy node may fail health checks before it is marked unhealthy (a single missed check is treated as transient).

45.0
auth_token str | None

Default bearer token sent to every node (override per node with register_node(..., auth_token=...)).

None
healthy_nodes property

List of currently healthy nodes.

nodes property

Read-only view of registered nodes.

check_health() async

Check health of all registered nodes.

Makes HTTP requests to each node's /health endpoint.

Returns:

Type Description
dict[str, dict[str, Any]]

Dict mapping node_id to health status.

cluster_status() async

Aggregate status across all nodes.

Returns:

Type Description
dict[str, Any]

Dict with cluster-wide summary and per-node details.

get_node(node_id)

Get information about a registered node.

Parameters:

Name Type Description Default
node_id str

Node identifier.

required

Returns:

Type Description
NodeInfo

Raises:

Type Description
KeyError

If node doesn't exist.

get_node_status(node_id) async

Get detailed status from a specific node via HTTP.

Parameters:

Name Type Description Default
node_id str

Node to query.

required

Returns:

Type Description
dict[str, Any]

Full status from the node.

Raises:

Type Description
KeyError

If node doesn't exist.

RuntimeError

If aiohttp is not available.

inject_event_on_node(node_id, process_name, payload, trigger_type='remote') async

Inject a trigger event on a remote node.

Parameters:

Name Type Description Default
node_id str

Target node.

required
process_name str

Target process.

required
payload Any

Event payload.

required
trigger_type str

Type of trigger event.

'remote'

Returns:

Type Description
dict[str, Any]

Response from the node.

Raises:

Type Description
RuntimeError

If the node rejects the request (non-2xx).

register_node(node_id, url, metadata=None, *, auth_token=None)

Register a runtime node with the coordinator.

Parameters:

Name Type Description Default
node_id str

Unique node identifier.

required
url str

Base URL for the node's transport API.

required
metadata dict[str, Any] | None

Optional additional metadata.

None
auth_token str | None

Bearer token for this node's transport (defaults to the coordinator's auth_token).

None

Returns:

Type Description
NodeInfo

The registered :class:NodeInfo.

start_health_monitor() async

Start the background health monitoring loop.

start_process_on_node(node_id, process_name) async

Start a process on a specific node.

Parameters:

Name Type Description Default
node_id str

Target node.

required
process_name str

Process to start.

required

Returns:

Type Description
dict[str, Any]

Response from the node.

Raises:

Type Description
RuntimeError

If the node rejects the request (non-2xx).

stop_health_monitor() async

Stop the health monitoring loop.

stop_process_on_node(node_id, process_name) async

Stop a process on a specific node.

Parameters:

Name Type Description Default
node_id str

Target node.

required
process_name str

Process to stop.

required

Returns:

Type Description
dict[str, Any]

Response from the node.

Raises:

Type Description
RuntimeError

If the node rejects the request (non-2xx).

unregister_node(node_id)

Remove a node from the cluster.

Parameters:

Name Type Description Default
node_id str

Node to remove.

required

Raises:

Type Description
KeyError

If node doesn't exist.

NodeInfo

promptise.runtime.distributed.NodeInfo dataclass

Information about a runtime node in the cluster.

Attributes:

Name Type Description
node_id str

Unique node identifier.

url str

Base URL for the node's transport API.

last_heartbeat float

Timestamp of last successful health check.

status str

Current node status.

process_count int

Number of processes on this node.

metadata dict[str, Any]

Additional node metadata.

is_healthy property

Check if node has had a recent heartbeat.

to_dict()

Serialize to JSON-compatible dict (never includes credentials).

DiscoveredNode

promptise.runtime.distributed.DiscoveredNode dataclass

A discovered runtime node.

Attributes:

Name Type Description
node_id str

Unique node identifier.

url str

Base URL for the node's transport API.

discovered_at float

Timestamp when the node was discovered.

metadata dict[str, Any]

Additional node metadata.

to_dict()

Serialize to JSON-compatible dict.

StaticDiscovery

promptise.runtime.distributed.StaticDiscovery

Static discovery from a known list of node URLs.

Suitable for fixed-topology deployments where all node addresses are known at configuration time.

Parameters:

Name Type Description Default
nodes dict[str, str] | None

Mapping of node_id → URL.

None
discover() async

Return all statically configured nodes.

register(node_id, url, metadata=None) async

Add a node to the static list.

unregister(node_id) async

Remove a node from the static list.

RegistryDiscovery

promptise.runtime.distributed.RegistryDiscovery

In-process registry for node discovery.

Nodes register themselves with the registry, and other nodes can discover them. This is suitable for single-process testing or when a shared registry object is accessible to all nodes.

For production deployments across machines, use with a central coordinator (the coordinator can hold the RegistryDiscovery instance and expose it via its HTTP API).

Parameters:

Name Type Description Default
ttl float

Time-to-live for registrations in seconds. After this period without re-registration, nodes are considered stale and removed from discovery.

60.0
discover() async

Return all registered nodes, pruning stale entries.

heartbeat(node_id) async

Update a node's registration timestamp.

Parameters:

Name Type Description Default
node_id str

Node to refresh.

required
register(node_id, url, metadata=None) async

Register a node for discovery.

unregister(node_id) async

Remove a node from the registry.


Lifecycle hooks

HookManager

promptise.runtime.hooks.HookManager

Event-keyed dispatcher for runtime lifecycle hooks.

Supports per-event registration with optional once semantics, ordered dispatch by priority, per-hook timeouts, exception isolation (one broken hook never blocks the rest), and programmatic deregistration by ID or tag.

Coroutine safety: all mutation and dispatch are guarded by an asyncio lock. It is safe to register hooks from inside a hook callback. A hook must not await a lifecycle method of the process that dispatched it (stop(), start()): schedule it with asyncio.create_task instead.

Parameters:

Name Type Description Default
default_timeout float | None

Seconds a hook may run when it was registered without a timeout (None: no limit, the default for a standalone manager; the manager an :class:AgentProcess creates uses 30 seconds).

None
clear(event=None)

Remove all hooks.

Parameters:

Name Type Description Default
event HookEvent | None

If provided, clear only this event's hooks; otherwise clear everything.

None
dispatch(event, *, process_id='', data=None, metadata=None) async

Fire all hooks registered for event.

Hooks run in priority order. Exceptions other than :class:HookBlocked are caught, logged, and recorded in the returned :class:DispatchResult — they never propagate to the caller (a fail_closed hook's error also blocks). A hook that exceeds its timeout is cancelled and recorded as an error. HookBlocked short-circuits remaining hooks and returns a blocked result.

Hooks with once=True are removed from the registry before they run, so each runs at most once, whatever happens.

Parameters:

Name Type Description Default
event HookEvent

The event to fire.

required
process_id str

Identifier of the dispatching process.

''
data dict[str, Any] | None

Event payload. Hooks get a copy as ctx.data: they may change it in place or assign a new dict to ctx.data (both are honored, and a later hook sees the earlier one's version); what ctx.data holds after the last hook is :attr:DispatchResult.data. For which keys the runtime reads back (e.g. prompt of user_prompt_submit), see :class:HookEvent.

None
metadata dict[str, Any] | None

Per-dispatch metadata.

None

Returns:

Name Type Description
A DispatchResult
has_hooks(event)

Whether any hook is registered for event (cheap; no lock).

on(event, *, once=False, priority=0, tag=None, timeout=None, fail_closed=False)

Decorator form of :meth:register.

Example::

@hooks.on(HookEvent.USER_PROMPT_SUBMIT)
async def audit(ctx):
    log(ctx.data["prompt"])

@hooks.on(HookEvent.SESSION_START, once=True)
async def greet(ctx):
    print("hello!")
register(event, callback, *, once=False, priority=0, tag=None, timeout=None, fail_closed=False)

Register a hook callback for an event.

Parameters:

Name Type Description Default
event HookEvent | str

Which :class:HookEvent to subscribe to (or its value, e.g. "session_start").

required
callback HookCallable

Async function (or object with an async __call__) taking a single :class:HookContext.

required
once bool

If True, the hook auto-deregisters after its first invocation (successful or not). It runs at most once, even when several dispatches of the event overlap.

False
priority int

Higher priority hooks run first within an event. Default 0. Use negative priorities to run late.

0
tag str | None

Optional string tag for grouped deregistration with :meth:remove_by_tag.

None
timeout float | None

Seconds the hook may run (default: the manager's default_timeout). A hook that takes longer is cancelled and recorded as an error.

None
fail_closed bool

If True, an error or timeout of this hook blocks the action (for a blockable event) instead of being ignored. Use it for hooks that enforce a policy.

False

Returns:

Type Description
str

A hook ID suitable for passing to :meth:remove.

Raises:

Type Description
TypeError

callback is not async.

ValueError

event is not a :class:HookEvent value, or timeout is not positive.

registered(event=None)

Return the hook IDs currently registered.

Parameters:

Name Type Description Default
event HookEvent | None

If provided, filter to this event.

None
remove(hook_id) async

Deregister a single hook by its ID.

Returns:

Type Description
bool

True if the hook was found and removed, False otherwise.

remove_by_tag(tag) async

Deregister every hook with a matching tag.

Returns:

Type Description
int

Number of hooks removed.

HookEvent

promptise.runtime.hooks.HookEvent

Bases: str, Enum

Lifecycle events that a :class:HookManager can dispatch.

There are 13 events. :class:~promptise.runtime.AgentProcess dispatches each of them at the moment described below; ctx.data holds the listed keys (secret values redacted). Events in :data:BLOCKABLE_EVENTS can be refused by raising :class:HookBlocked; for the others a block is logged and ignored. They are deliberately coarse: for per-node / per-tool introspection of a single run, use :mod:promptise.engine.hooks instead.

HookContext

promptise.runtime.hooks.HookContext dataclass

Context passed to every hook callback.

Attributes:

Name Type Description
event HookEvent

The event that is firing.

process_id str

Identifier of the :class:AgentProcess dispatching the hook (its name). May be empty for top-level dispatch.

timestamp datetime

timezone.utc timestamp of dispatch.

data dict[str, Any]

Free-form event payload. Hook implementations may read and (for some events, e.g. USER_PROMPT_SUBMIT) mutate this dict to alter the subsequent runtime behavior; the caller reads it back from :attr:DispatchResult.data.

metadata dict[str, Any]

Additional per-dispatch metadata (process_name, process_id), not typically mutated by hooks.

get(key, default=None)

Shorthand for self.data.get(key, default).

set(key, value)

Shorthand for self.data[key] = value.

HookBlocked

promptise.runtime.hooks.HookBlocked

Bases: Exception

Raised by a hook to block the action that triggered it.

The dispatcher catches this specifically and returns a blocked=True outcome so callers can abort the in-progress operation (e.g. cancel a tool call, refuse a user prompt). Only the events in :data:BLOCKABLE_EVENTS can be blocked.

DispatchResult

promptise.runtime.hooks.DispatchResult dataclass

Outcome of a :meth:HookManager.dispatch call.

Attributes:

Name Type Description
fired int

Number of hooks that ran.

blocked bool

True if any hook raised :class:HookBlocked (or a fail_closed hook failed).

reason str

Block reason, if any.

errors list[tuple[str, Exception]]

List of (hook_id, exception) tuples for hooks that raised an unexpected exception or timed out. These are logged and swallowed so one broken hook does not take down the runtime.

data dict[str, Any]

The event payload after the hooks ran (hooks may mutate it, e.g. rewrite data["prompt"]).

ShellHook

promptise.runtime.shell_hook.ShellHook dataclass

Runtime hook that shells out to an external command.

The command receives a JSON event on stdin and may write a JSON response to stdout. Use ShellHook.callback as the callback argument to :meth:HookManager.register.

Attributes:

Name Type Description
command str | list[str]

The program and its arguments: a list (used as is) or a string (split with :func:shlex.split, so quoting works as in a terminal). With shell=True a string run by /bin/sh -c.

timeout float

Max seconds the command may run before being killed. Default 5.0.

cwd str | None

Working directory for the subprocess. Default current directory.

env dict[str, str] | None

Extra environment variables for the program (added to the minimal environment, see the module docstring). Values may be ${VAR} / ${VAR:-default} references to the host environment, resolved each time the program runs.

shell bool

If True, run via the shell instead of an exec. Only for commands written in code: the command string is passed to the shell as is (the payload never is).

encoding str

stdin/stdout encoding. Default "utf-8".

inherit_env bool

Pass the whole host environment (in 1.4.3 and earlier the default). Off by default so credentials in the environment don't reach hook programs.

env_passthrough list[str]

Names of host environment variables to pass through (when set), in addition to :data:SAFE_ENV_VARS.

max_output_bytes int

Largest stdout accepted (default 64 KiB); the program is killed and the hook fails beyond it.

hook = ShellHook(command="./hooks/audit_prompt.sh")
hooks.register(HookEvent.USER_PROMPT_SUBMIT, hook.callback)

The script audit_prompt.sh might look like::

#!/usr/bin/env bash
# Read JSON from stdin, log the prompt, allow it through.
prompt=$(jq -r .data.prompt)
echo "[$(date)] $prompt" >> /var/log/agent_prompts.log
echo '{}'
callback(ctx) async

Async hook callback — pass this to :meth:HookManager.register.

Serializes the context, runs the command, parses any JSON reply, and applies its directives (mutate ctx.data, raise :class:HookBlocked).

run(ctx) async

Execute the underlying command and return the parsed result.

Public so it can be unit-tested or invoked manually.

Raises:

Type Description
TimeoutError

The program ran longer than timeout.

RuntimeError

It exited non-zero.

ValueError

Its output was too large or not a JSON object.

ShellHookResult

promptise.runtime.shell_hook.ShellHookResult dataclass

Parsed result returned by an external shell hook.


Dashboard

RuntimeDashboard

promptise.runtime.RuntimeDashboard

Full-screen tabbed terminal dashboard for the Agent Runtime.

Follows the same architecture as the MCP server's :class:~promptise.mcp.server._dashboard.Dashboard:

  • 7 tabs -- Overview, Processes, Triggers, Context, Logs, Events, Commands
  • Arrow-key / number-key navigation
  • Live refresh -- ~250ms in a daemon thread
  • Log capture -- Python logger output in Logs tab
  • Command input -- interactive process control

Parameters:

Name Type Description Default
state RuntimeDashboardState

:class:RuntimeDashboardState instance (shared with data collector).

required
runtime AgentRuntime | None

Optional :class:AgentRuntime for command execution.

None
start()

Start the live dashboard (takes over the terminal).

Call it from the runtime's event loop (as promptise runtime start --dashboard does): process commands are run on that loop.

stop()

Stop the dashboard and restore the terminal.

RuntimeDashboardState

promptise.runtime.RuntimeDashboardState dataclass

Thread-safe shared state for the runtime dashboard.

Mirrors the MCP dashboard's :class:DashboardState pattern but tracks runtime-specific data: processes, triggers, world state, and journal entries.

record_command(result)

Record a command executed.

record_event(event)

Record a trigger event received.

record_invocation(log)

Record a completed agent invocation.

record_journal(entry)

Record a journal entry.

RuntimeDataCollector

promptise.runtime.RuntimeDataCollector

Collects real-time data from a running AgentRuntime into DashboardState.

Runs as a background thread, periodically polling the runtime for process status, context state, and trigger info. Trigger events and finished runs arrive through task_created / task_completed hooks it registers on every process (Events and Recent Activity panels, trigger fire counts), and the journal panel is read from each process's journal on the runtime's event loop.

Parameters:

Name Type Description Default
runtime AgentRuntime

The runtime to watch.

required
state RuntimeDashboardState

The shared dashboard state.

required
interval float

Seconds between snapshots.

0.5
loop AbstractEventLoop | None

The event loop the runtime runs on (default: the running loop when the collector is created, if any). Without one the journal panel stays empty.

None
start()

Start the data collection thread.

stop()

Stop collecting and remove the hooks it registered.


Exceptions

RuntimeBaseError

promptise.runtime.exceptions.RuntimeBaseError

Bases: RuntimeError

Base exception for all runtime module errors.

ProcessStateError

promptise.runtime.exceptions.ProcessStateError

Bases: RuntimeBaseError

Raised when a lifecycle state transition is invalid.

The runtime raises its subclass :class:~promptise.runtime.lifecycle.StateError, which is also a :class:ValueError (what it was in 1.4.3 and earlier), so except ProcessStateError, except StateError and except ValueError all catch an invalid transition.

Attributes:

Name Type Description
process_id

The process that attempted the transition (None when a :class:~promptise.runtime.lifecycle.StateError was raised without one).

current_state

The process's current state.

attempted_state

The target state that was rejected.

ManifestError

promptise.runtime.exceptions.ManifestError

Bases: RuntimeBaseError

Raised when .agent manifest loading fails (I/O or parse error).

ManifestValidationError

promptise.runtime.exceptions.ManifestValidationError

Bases: ManifestError

Raised when .agent manifest schema validation fails.

Attributes:

Name Type Description
errors

Pydantic validation error details (if available).

file_path

Path to the manifest file that failed validation.

TriggerError

promptise.runtime.exceptions.TriggerError

Bases: RuntimeBaseError

Raised when a trigger fails to start, stop, or produce events.

JournalError

promptise.runtime.exceptions.JournalError

Bases: RuntimeBaseError

Raised when journal read, write, or replay fails.