Repository navigation
Promptise Foundry 1.5.1
Promptise now works with the MCP Python SDK 2.x as well as 1.x, the SPIFFE extra installs, sandboxed code no longer runs as root, PII patterns stop redacting parts of numbers, and a set of runtime fixes, among them a stop() race that could run a finished run again.
Upgrade notes
- The MCP SDK 2.x is supported, and a new install gets it. The requirement is
mcp>=1.9,<3(was<2), so Promptise installs next to packages that need the 2.x SDK, and 2.x clients reach a Promptise server with the 2026-07-28 protocol revision. Promptise's APIs are unchanged. Code of your own that reads MCP SDK objects (theTool,CallToolResult... thatMCPClientandTestClientreturn) follows the SDK: 2.x has snake_case attributes (tool.input_schema,result.is_error). Pin"mcp>=1.9,<2"to keep the 1.x SDK.pydantic-settingsis now a direct dependency (the 1.x SDK installed it, 2.x doesn't). - On a 2026-07-28 connection the server can't send requests to the client, so elicitation (including
ElicitationApprover) and sampling are declined there, and gated calls fail closed. Promptise'sMCPClientalways uses the handshake, where they work; with the 2.x SDK's own client useClient(..., mode="legacy"). - Sandboxed code runs as
nobody.SandboxConfig.user(also in a.superagentsandbox:section) defaults to"65534:65534"; before, the container ran as the image's default user, root inpython:3.11-slim. The workspace (HOME) and/tmpstay writable andpip installstill installs into the workspace. Setuser="root"for the old behavior, or the image's user name for a custom image. promptise[identity-spiffe]depends onspiffe(py-spiffe), the package that exists on PyPI. 1.5.0'spyspiffe>=1.0could not be installed, so[identity-spiffe]and[identity-all]were unusable.- PII redaction changed for some inputs. A number pattern now matches a whole number or nothing (an order number
2024-118-5521is no longer redacted as a phone number, a 14-digit value afterNPIno longer as an NPI); a spaced IBAN is redacted whole as[IBAN](it was partly redacted as an Aadhaar number); contextual bank accounts take up to 34 digits. - Some trigger IDs change once. A trigger whose ID hashed a function
filter_expression(its ID changed on every start anyway), or acustom_configvalue with a credential-like key or a URL with user info, gets a new stable ID: those values are now masked before hashing, as documented. promptise runtime starthas an in-process event bus.eventtriggers and escalations with anevent_typenow work between the processes of one CLI runtime (aneventtrigger could not start before).- A run refused by
max_runs_per_dayis dead-lettered (reason"budget: max_runs_per_day") and itstask_completedoutcome is"refused"; it reported"success"and was lost. - An unset
${VAR}in a manifest raisesManifestEnvVarError, which is both aManifestErrorand theEnvVarNotFoundErrorraised before. - An
async deffilter_expressionis rejected when theTriggerConfigis built; it was never awaited, so every event passed.
Fixed
- mcp 2.x:
import promptisefailed (McpErrorisMCPError) and the server and client could not run on it. A compatibility layer (promptise.mcp._compat,promptise.mcp.server._sdk) registers the server's handlers on either SDK's lowlevel server, reads protocol fields in either spelling, hands the SDKhttpx2objects with 2.x, keeps HTTP 401/403/404 statuses that 2.x no longer raises (soMCPConnectionRejectedErrorand session re-initialisation behave as with 1.x), reports a failedbearer_token_providerasMCPCredentialError, and keyslist_changednotifications andSessionStateon the connection (2.x builds a new session object per request). The 1.x code paths are unchanged. - SPIFFE SDK mode never worked -- it imported
pyspiffe.workloadapi...and calledWorkloadApiClient(spiffe_socket_path=...)/fetch_jwt_svid(audiences=...). It now usesspiffe.WorkloadApiClient(socket_path=...),fetch_jwt_svid(audience=...)andJwtSvid.token, also forAgentIdentity.auto()withSPIFFE_ENDPOINT_SOCKETset; a missing or invalid socket raisesCredentialAcquisitionError. - PII:
phone_usmatched the last ten digits of longer numbers (2024-118-5521became2[PHONE]); spaced IBANs were read as Aadhaar / My Number and partly redacted (an IBAN is now detected in groups of four, checked with the mod-97 checksum, and findings inside it are dropped); andexp 12/2025became[CARD_EXPIRY]25-- EIN, NPI, BSN, DEA, Medicare, NHS, NY licence, passport, PAN, VIN, SWIFT, DOB, IPv4, MRN, SSN, ITIN, SIN, RRN and NDC patterns had the same boundary problem. stop()during a finished run'stask_completedhook dead-lettered the event ("process stopped", "run cancelled"), andredeliver_dead_letters()then ran the finished run again, repeating its tool calls. The event is released before the hooks run; only events whose run did not finish are dead-lettered.redeliver_dead_letters()didn't wake an idle-suspended process, so redelivered events never ran.- Trigger IDs with a function filter changed on every start (they hashed
repr(), which has a memory address); they hash the function's module and name. - Event and message triggers dropped events silently when their 100-event buffer was full. They count them (
dropped_eventsinstatus()["triggers"]) and the process dead-letters them ("trigger buffer full"). promptise runtime startcould not use custom trigger types (validate passed, start failed with "Unknown trigger type").startandvalidatetake--import MODULEto import the module that registers them, and both report an unknown type before anything starts.promptise runtime validateprinted a traceback for an unset${VAR}and, for a directory, stopped at that file; it reports the file and goes on.promptise runtime startprinted a traceback too (for a directory, after "All processes stopped"); it now checks every manifest before starting anything.- An approval-denied call used up
max_irreversible_per_run(counted when the call starts, before the gate asks). A denied call gives its irreversible action back; it still counts as a tool call and against the cost budget. - An unreachable cluster node raised
aiohttpor timeout errors from remote operations; they raiseNodeUnreachableError, aRuntimeError, as documented. - The
low_confidencemission escalation had noprocess_name, unlike the budget and health payloads. - A lost connection could be reported as
Failed to call tool 'x':(an empty reason), depending on whether the request or the session noticed first; it is reported asConnection to ... was lost (...)on both SDKs. This also fixes a flaky test (test_auto_reconnect_off_reports_the_lost_session). - Dashboard: the Processes tab hid Name and Model below about 160 columns (it now drops the least important columns first); the Logs tab's log panel pushed the journal panel off the screen; the header's Events counter showed runs; the Events tab's Payload column was always
-(a newpayload_summaryin thetask_createdhook data fills it). GET /triggersshowed the password of a URL incustom_config(scheme://user:pass@host); URL user info is masked.- Messages:
COMMANDS_NOT_ALLOWEDsuggested callback hooks, which a JSON deploy can't carry;rewind --plansaid "N of M entries after it" with N counting the target; the escalation warning without an event bus now says how to fix it; a refused cluster node was logged with doubled quotes; and every budget refusal logged LangChain's "Error in BudgetGuard.on_tool_start callback" warning (the guard now logs the refusal at INFO). - Manifests: unknown top-level,
config:andmemory:keys get "did you mean" hints like the other sections.
Changed
- New:
SandboxConfig.user;promptise.runtime.InProcessEventBus;promptise.runtime.ManifestEnvVarError;promptise.runtime.distributed.NodeUnreachableError;promptise.approval.on_approval_denied;--importforpromptise runtime startandvalidate. - CI: a
test-mcp-v1job runs the MCP tests and mypy withmcp<2; the main matrix resolves to the 2.x SDK. - Docs: installation (MCP SDK versions, identity extras), SPIFFE provider, sandbox user, MCP server SDK compatibility, triggers, processes, hooks, budget, coordinator and CLI pages describe the changes above.
- Docs corrected where the code was right: a run refused by
max_runs_per_daydoes emitbudget.exceeded(budget.md); every journal entry has anentry_id, sosecret_accessandmission_evaluationentries can be rewind targets (journal index, rewind.md); the rewind worked example's undone count matches its plan and the resume comes before the checkpoint; manifests' triggers are fully validated at load andget_secretexists only in open mode (manifests.md); hookenvvalues stay${VAR}until the hook runs (hooks.md); the dashboard collector fills the Events, Recent Activity and journal panels, and its trigger IDs are the stable ones (dashboard.md). - The
purge_userdocstrings (cache, memory, observability) describe deleting one user's data on request instead of naming a law (docs only).