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: |
None
|
event_notifier
|
Any | None
|
Optional :class: |
None
|
hooks
|
HookManager | None
|
Optional shared :class: |
None
|
deploy_policy
|
DeployPolicy | None
|
Where the config came from
(:class: |
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 |
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
|
|
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: |
required |
Returns:
| Type | Description |
|---|---|
str
|
The trigger's stable ID (see :func: |
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
|
|
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 ( |
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 |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
The process has no mission ( |
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'
|
Raises:
| Type | Description |
|---|---|
KeyError
|
No trigger has trigger_id (for |
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 |
'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
|
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: |
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 |
'context'
|
priority
|
str
|
|
'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
( |
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
|
|
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
|
|
ValueError
|
|
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: |
status()
¶
Serializable status snapshot.
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
Dict with process name, state, |
dict[str, Any]
|
queue and dead-letter sizes, |
dict[str, Any]
|
last start; |
dict[str, Any]
|
( |
dict[str, Any]
|
|
dict[str, Any]
|
and |
dict[str, Any]
|
recovery) and, per enabled subsystem, |
dict[str, Any]
|
|
dict[str, Any]
|
(:meth: |
dict[str, Any]
|
|
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), |
'manual'
|
Raises:
| Type | Description |
|---|---|
StateError
|
The process is not |
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: |
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: |
None
|
hooks
|
Any | None
|
Optional :class: |
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: |
None
|
Returns:
| Type | Description |
|---|---|
AgentProcess
|
The created :class: |
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: |
None
|
hooks
|
Any | None
|
Optional :class: |
None
|
Returns:
| Type | Description |
|---|---|
AgentRuntime
|
A new :class: |
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: |
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 |
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 |
required |
name_override
|
str | None
|
Override the process name from the manifest. |
None
|
Returns:
| Type | Description |
|---|---|
AgentProcess
|
The created :class: |
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 |
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 |
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]
|
|
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: |
None
|
memory_provider
|
Any | None
|
Optional
:class: |
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 |
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
|
|
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: |
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'
|
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: |
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: |
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 ( |
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.
|
100
|
enabled
|
bool
|
|
True
|
append(message)
¶
Add a single message to the buffer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
message
|
dict[str, Any]
|
Message dict with |
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: |
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 |
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. |
instructions |
str | None
|
System prompt for the agent. |
servers |
dict[str, Any]
|
MCP server specifications (same format as |
triggers |
list[TriggerConfig]
|
Trigger configurations. |
journal |
JournalConfig
|
Journal configuration. Off by default ( |
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.
|
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: |
hooks |
list[HookConfig]
|
Runtime lifecycle hooks (:class: |
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.
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. Withdiscovery_method="registry"nodes register themselves atPOST /cluster/nodesof its transport (and are dropped when they stop re-registering fornode_timeoutseconds); with"static"the nodes are listed instatic_nodes. The coordinator also lists itself.coordinator_urlset: this node registers with the coordinator at that URL when it starts, re-registers everyheartbeat_intervalseconds (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
|
node_id |
str | None
|
This node's ID (default: |
transport_host |
str
|
Interface the transport binds (default loopback). |
transport_port |
int
|
Port of the transport ( |
advertise_url |
str | None
|
URL other nodes reach this one at (default
|
auth_token |
SecretStr | None
|
The cluster bearer token, or an |
allow_unauthenticated |
bool
|
Allow a non-loopback transport or
coordinator without |
discovery_method |
Literal['registry', 'static', 'multicast']
|
|
static_nodes |
dict[str, str]
|
|
allowed_node_hosts |
list[str]
|
For a registry coordinator: the host names
or IP addresses a registering node's URL may name ( |
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 |
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 |
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). |
conversation_history |
bool
|
Set to |
conversation_clear_on_stop |
bool
|
Clear the conversation history when
the process stops. |
state_tool |
bool
|
Give the agent an |
file_mounts |
dict[str, str]
|
Mapping of logical name → filesystem path. |
env_prefix |
str
|
Only expose environment variables with this prefix
(:meth: |
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.
|
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 ( |
sandbox_custom_tools |
bool
|
Run agent-written tools with restricted
builtins and no imports ( |
allow_process_spawn |
bool
|
Agent can spawn new processes within the
runtime. Defaults to |
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 |
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.
|
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: |
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 |
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: |
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: |
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
|
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: |
CREATED
|
process_id
|
str | None
|
Owner name shown in :class: |
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: |
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: |
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). |
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 |
|
|
attempted_state |
|
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: |
cron_expression |
str | None
|
Cron expression ( |
cron_timezone |
str | None
|
IANA time zone the cron expression is read in
( |
webhook_path |
str
|
URL path for the webhook endpoint ( |
webhook_port |
int
|
Listening port ( |
webhook_host |
str
|
Interface to bind ( |
hmac_secret |
SecretStr | None
|
Shared secret for request signatures ( |
signature_scheme |
Literal['generic', 'github', 'stripe']
|
How the signature is computed and sent:
|
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
( |
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 ( |
watch_path |
str | None
|
Directory to watch ( |
watch_patterns |
list[str]
|
Glob patterns ( |
watch_events |
list[str]
|
Filesystem events to react to ( |
watch_debounce_seconds |
float
|
Window in which events for the same file
are merged into one ( |
watch_recursive |
bool
|
Also watch subdirectories ( |
watch_backend |
Literal['auto', 'watchdog', 'polling']
|
How changes are detected ( |
watch_poll_interval |
float
|
Seconds between scans with the polling
backend ( |
watch_max_pending_events |
int
|
How many changed files the trigger holds
while the process is busy ( |
event_type |
str | None
|
EventBus event type string ( |
event_source |
str | None
|
Optional source filter ( |
topic |
str | None
|
MessageBroker topic ( |
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 |
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: |
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 ( |
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. |
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. |
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'
|
port
|
int
|
TCP port to bind to. |
9090
|
host
|
str
|
Host/IP to bind to. Defaults to |
'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 |
None
|
signature_scheme
|
Literal['generic', 'github', 'stripe']
|
|
'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 |
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 |
None
|
max_body_bytes
|
int
|
Largest request body accepted, in bytes (default
1 MiB). Larger requests get |
DEFAULT_MAX_BODY_BYTES
|
Raises:
| Type | Description |
|---|---|
ValueError
|
Unknown |
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. |
None
|
events
|
list[str] | None
|
Event types to react to ( |
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'
|
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
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 |
required |
event_type
|
str
|
Event type string to listen for (e.g. |
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
|
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 |
required |
topic
|
str
|
Topic to subscribe to (supports wildcards like |
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 |
None
|
broker
|
Any | None
|
Required for |
None
|
Returns:
| Type | Description |
|---|---|
BaseTrigger
|
A trigger instance satisfying :class: |
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 |
required |
factory
|
TriggerFactory
|
Callable with signature
|
required |
overwrite
|
bool
|
If |
False
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If type_name is already registered and
overwrite is |
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
|
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
|
|
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 |
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 |
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 |
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. |
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
|
|
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. |
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; |
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 |
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: |
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]
|
|
dict[str, Any]
|
|
dict[str, Any]
|
the start or the last :meth: |
dict[str, Any]
|
|
dict[str, Any]
|
ISO timestamp) or |
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
|
|
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 |
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
( |
on_complete |
Literal['stop', 'continue', 'suspend'] | None
|
Deprecated and ignored. It was never read: what
happens on completion is decided by |
escalation |
EscalationTarget | None
|
Escalation target for low confidence. |
success_check |
Callable[[Any], bool | None] | None
|
Optional programmatic check, called with the
:class: |
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 |
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 |
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'
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The payload for a |
dict[str, Any]
|
|
dict[str, Any]
|
|
dict[str, Any]
|
|
dict[str, Any]
|
|
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 |
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
|
|
skipped |
bool
|
|
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 ( |
default_ttl |
float | None
|
Default TTL in seconds ( |
ttls |
dict[str, float]
|
Per-secret TTL overrides in seconds. Every name must be a
key of |
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'
|
env_references
|
bool | Collection[str]
|
Which environment variables |
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 |
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
|
list[dict[str, Any]]
|
expiry), |
list[dict[str, Any]]
|
not active), |
list[dict[str, Any]]
|
|
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: |
False
|
Raises:
| Type | Description |
|---|---|
SecretResolutionError
|
A referenced environment variable is
not set and has no default, or |
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 |
required |
new_value
|
str
|
The new value (non-empty). |
required |
ttl
|
float | None
|
Seconds the new value is valid for, from now. |
None
|
Raises:
| Type | Description |
|---|---|
SecretNotFoundError
|
The name isn't a configured secret (a
:class: |
ValueError
|
|
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 |
20
|
scanner
|
Any | None
|
Optional guardrail scanner (an object with
|
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]]
|
|
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: |
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. |
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 — |
backend |
Literal['file', 'memory']
|
Storage backend. |
path |
str
|
Directory for the file backend. |
recover_on_start |
bool
|
On a process object's first :meth: |
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 ( |
data |
dict[str, Any]
|
Entry payload. |
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: |
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.
- Load the last checkpoint (if any).
- Read all entries after the checkpoint.
- 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 |
|
dict[str, Any]
|
|
|
checkpointed |
dict[str, Any]
|
|
dict[str, Any]
|
|
|
dict[str, Any]
|
|
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: |
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: |
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 |
required |
mode
|
RewindMode
|
Selectivity of the rewind. See :class: |
required |
actor
|
str
|
Who initiated the rewind — recorded in the rewind
entry. Defaults to |
'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
|
Returns:
| Name | Type | Description |
|---|---|---|
A |
RewindPlan
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
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 |
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: |
entrypoint |
str | None
|
Reserved; not read by the runtime. Python hooks are
registered in code ( |
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 |
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: |
required |
host
|
str
|
Bind address (default |
'127.0.0.1'
|
port
|
int
|
Port to listen on (default |
9100
|
auth_token
|
str | None
|
Bearer token required on every route except
|
None
|
on_shutdown
|
Callable[[], Any] | None
|
Enables |
None
|
allow_command_deploys
|
bool
|
Let |
False
|
allow_env_references
|
bool
|
Let deployed configs use |
False
|
allowed_env_vars
|
Iterable[str] | None
|
The variables deployed configs may reference
when |
None
|
data_dir
|
str | Path | None
|
The directory deployed configs' file system paths must
lie in: |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
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. |
required |
token
|
str | None
|
Bearer token for authentication (the server's
|
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]
|
|
dict[str, Any]
|
(whether it is already listening) and |
dict[str, Any]
|
( |
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 |
'normal'
|
Returns:
| Type | Description |
|---|---|
str | None
|
The agent's answer text, or |
str | None
|
within timeout (the server answers |
Raises:
| Type | Description |
|---|---|
OrchestrationAPIError
|
For an error answer, e.g. |
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]
|
|
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. |
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]
|
|
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 |
required |
mode
|
str
|
|
'both'
|
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
The plan: |
dict[str, Any]
|
|
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
|
|
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 |
'context'
|
priority
|
str
|
One of |
'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
|
|
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 ( |
|
code |
The API's error code ( |
|
error_message |
The API's error message (or the raw body). |
|
error_id |
The id under which the server logged the failure's
details ( |
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: |
required |
config
|
DistributedConfig
|
Its |
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 |
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 checkGET /status— full runtime statusGET /processes— list all processesGET /processes/{name}/status— single process statusPOST /processes/{name}/start— start a processPOST /processes/{name}/stop— stop a processPOST /processes/{name}/restart— restart a processPOST /processes/{name}/event— inject a trigger event
With a coordinator (the node hosting the cluster coordinator):
GET /cluster/nodes— the registered nodes and their healthPOST /cluster/nodes— register (or re-register) a node:{"node_id": ..., "url": ..., "metadata": {...}}; answers409with static discovery (the coordinator lists the nodes itself)DELETE /cluster/nodes/{node_id}— unregister a nodeGET /cluster/status— :meth:RuntimeCoordinator.cluster_status
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runtime
|
Any
|
The :class: |
required |
host
|
str
|
Host to bind to (default |
'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
|
None
|
allow_unauthenticated
|
bool
|
Allow a non-loopback |
False
|
coordinator
|
Any | None
|
A :class: |
None
|
discovery
|
Any | None
|
The coordinator's discovery: a
:class: |
None
|
allowed_node_hosts
|
list[str] | None
|
Hosts a registering node's URL may name
( |
None
|
Raises:
| Type | Description |
|---|---|
ValueError
|
|
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 |
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 |
None
|
Returns:
| Type | Description |
|---|---|
NodeInfo
|
The registered :class: |
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. |
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
|
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 |
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 |
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: |
required |
callback
|
HookCallable
|
Async function (or object with an async
|
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: |
None
|
timeout
|
float | None
|
Seconds the hook may run (default: the manager's
|
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: |
Raises:
| Type | Description |
|---|---|
TypeError
|
callback is not async. |
ValueError
|
event is not a :class: |
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: |
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: |
metadata |
dict[str, Any]
|
Additional per-dispatch metadata ( |
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: |
reason |
str
|
Block reason, if any. |
errors |
list[tuple[str, Exception]]
|
List of |
data |
dict[str, Any]
|
The event payload after the hooks ran (hooks may mutate
it, e.g. rewrite |
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: |
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
|
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: |
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 |
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: |
required |
runtime
|
AgentRuntime | None
|
Optional :class: |
None
|
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.
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
|
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 ( |
|
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.