Skip to content

v1.3.4

Choose a tag to compare

@cryxnet cryxnet released this 15 Oct 13:16
· 19 commits to main since this release

A patch release with fixes found while running the promptise.com guides. Upgrade with pip install -U promptise.

Highlights: max_agent_iterations=N now allows exactly N model calls; budget and hook stops raise GraphBudgetExceededError / GraphStoppedError instead of returning a raw tool result; invalid MCP tool calls over HTTP now go through auth, rate limiting and audit with a bounded error; credential redaction keeps JSON valid; context compaction no longer pushes models into re-read loops; the "Available tools" prompt block is opt-in (tool_summary). Read Changed before upgrading.

Fixed

  • max_agent_iterations=N ran N+1 model calls -- the engine counted a node execution after running it and stopped only once the count passed the limit, so build_agent(max_agent_iterations=N) (the engine's max_iterations) allowed N+1 node executions: N+1 model calls and N+1 rounds of tool calls in the default ReAct graph. N now allows exactly N node executions, which in the ReAct graph is N model calls. The build_agent docstring and the engine docs say what is counted.
  • BudgetHook stopped a run silently and returned a raw tool result as the answer -- when the budget ran out in the middle of a tool loop, ainvoke() returned normally with the last tool result as the last message and last_report.error unset. Such a run now raises GraphBudgetExceededError (a new GraphStoppedError, itself a GraphExecutionError) with reason ("Token budget exceeded: 5078/5000 tokens"), stopped_by, pending_tools and the report, like GraphIterationLimitError for iteration limits. Any hook that ends a run the same way (CycleDetectionHook, a custom hook) raises GraphStoppedError. A budget reached on the node that answered still returns the answer.
  • One BudgetHook capped an engine's lifetime, not a run -- tokens_used was never reset, so after the first run hit the cap every later run on the same engine stopped after its first node. The hook now counts the run's own tokens (GraphState.total_tokens), so every run, concurrent ones included, gets the whole budget; tokens_used / cost_used report the latest run.
  • Context compaction could make a model re-read the same results until its budget ran out -- a result longer than keep_result_chars was cut in the ledger with a note telling the model to call the tool again for the full text, and the re-read was cut again as soon as the next exchange started, so a model that re-read cut results one at a time could never have them all in full (reproduced with a scripted model: the default ReAct agent looped to its iteration limit). A call the model made more than once is now shown in full in the ledger (and shrunk last under max_tokens), and the excerpt says how much was cut and that a re-read is kept in full.
  • Schema-invalid tool calls skipped the server pipeline over HTTP and stdio, and echoed the whole input -- the MCP SDK (1.10+) validated a call's arguments against the tool's inputSchema before Promptise saw the call, so an invalid call was never authenticated, rate limited or audited, and came back as plain text, Input validation error: <jsonschema message>, quoting the offending value in full (a 1 MB string against max_length=10 produced a 1 MB error). The SDK's check is now off; the server validates every call itself, inside its pipeline, on every transport and in TestClient, and the error is the structured VALIDATION_ERROR envelope.
  • VALIDATION_ERROR envelopes could grow with the input -- one entry per invalid item, so a list of 50,000 bad values produced a 9 MB error. The envelope now lists at most 20 field errors (details.omitted_errors counts the rest), clips field paths and messages, and caps the message at 1,000 characters; it never includes the rejected value.
  • Redacting credentials corrupted JSON -- the connection-string patterns (postgres://, mysql://, mongodb://, redis://) and the password= / secret= patterns ran to the next whitespace, so on a JSON tool result they swallowed the closing quote, brace and comma: {"config": {"db": "postgres://..."}, "region": "eu"} became {"config": {"db": "[POSTGRES_CONNECTION] "region": "eu"}. Those patterns now stop at quotes and backslashes (URLs also at <>{}), and when the scanned text is a JSON document a redaction replaces text inside a string value only: the result parses to the same structure, JSON encoded inside a string stays valid, escape sequences are never split, and a number that has to be redacted becomes a quoted label.
  • mcp 2.x clients logged a 31-error WARNING on every connection to a Promptise server -- an mcp 2.x client (current Pydantic AI, OpenAI Agents SDK) opens with server/discover, which the 1.x SDK failed to validate, logging Failed to validate request: 31 validation errors for ClientRequest (94 lines) before answering -32602. The server now answers a request for any method it does not implement with -32601 Method not found and logs it at DEBUG; the client falls back to initialize as before. A notification of an unknown method is ignored. Works with every supported mcp version (>=1.9,<2).
  • The Quick Start's first agent raised TypeError -- "Your First Agent" calls build_agent(model=..., instructions=...), but servers was a required keyword, so the first example a new user ran failed with build_agent() missing 1 required keyword-only argument: 'servers'. servers is now optional (see Changed).
  • The Quick Start's "Add Tools" example never started its tool server -- the agent launched tools.py without --serve, so the child process ran the agent again (which launched another tools.py, and so on) instead of serving its tools, and the MCP handshake never happened. The example now passes args=["tools.py", "--serve"]. The same example in the blog posts "What is MCP", "Build an AI agent with Promptise" and "How to build an AI agent in Python" is fixed too (the last one also never called server.run()). Every python block on the Quick Start page now runs in the test suite with a scripted model, the stdio example against its real server.
  • Health: one anomaly silenced every other anomaly of its kind for 5 minutes -- HealthConfig.cooldown (300 s by default) was kept per anomaly type, so once the agent was reported stuck on one call, getting stuck on a different call (in the same run or in the next runs) was not reported: no anomaly, no health.anomaly event, no on_anomaly action. The cooldown now applies to the same anomaly only: the same stuck call (tool and arguments), the same loop (whichever call of the cycle it is caught at), or the same empty-response / error-rate signal. The health docs now explain the cooldown and how to use HealthMonitor as an in-run guard in a pre_tool hook (cooldown=0, so every repeat is reported).
  • The mission judge printed "No tools discovered" on every evaluation -- build_agent() printed [promptise] No tools discovered from MCP servers; agent will run without tools. to stdout whenever an agent had no tools, and the mission judge is built without tools, so a process with a mission wrote that line into the application's output once per evaluation. See Changed.
  • promptise audit verify --strict summary grammar -- with one warning it read "1 warning fail the check"; it now reads "1 warning fails the check under --strict".
  • Open mode: the model was not told that a tool it creates is usable only from its next run -- the run that calls create_tool keeps the agent and tool list it started with, but create_tool answered Tool 'x' created. Rebuild successful and its description said nothing about when the tool appears, so models kept looking for the tool and creating it again. See Changed.
  • Docs: sandbox_custom_tools=False does not give agent-written code full Python builtins -- docs/runtime/meta-tools.md said it did. The code runs with the same restricted builtins (no open, exec, eval) and may additionally import a fixed standard-library allowlist (json, math, re, datetime, ...); the docs and the _build_dynamic_tool docstring now say so.

Changed

  • The "Available tools" block is no longer added to the system prompt by default -- every PromptNode with tools appended a summary of them (parameters and the first 120 characters of each description) to its system prompt, on top of the tool definitions the provider already receives. On the 40-tool test server that is 997 tokens on every model call (a third on top of the 3,043 tokens of tool definitions); a 48-tool guide run measured 1,882 of a 6,843-token first request. New tool_summary option on build_agent, PromptGraphEngine and PromptNode: "off" (the default), "names" (one line of names, 178 tokens on the same server) or "full" (the previous block). Set tool_summary="full" to keep the 1.3.3 prompt. A string default now renders quoted (cursor: string="", was cursor: string=).
  • The compaction ledger no longer invites re-reading "something that changes over time" -- its header now tells the model to use the listed facts and call again only for the rest of a cut result or when it is waiting for something to change, such as a job's status. The managed pattern's instructions say the same. Repeated calls still run the tool, so polling works as before.
  • max_agent_iterations / PromptGraphEngine(max_iterations=...) allow one node execution fewer than in 1.3.3 (see Fixed): an agent that needed exactly N+1 steps under a limit of N now raises GraphIterationLimitError; raise the limit by one to keep the old allowance.
  • A BudgetHook (or other hook) that stops a run mid tool loop raises instead of returning (see Fixed). Catch GraphBudgetExceededError / GraphStoppedError.
  • Input validation runs inside the middleware chain -- a call whose arguments fail validation now goes through the server's middleware (authentication, rate limiting, audit, in your order) and the tool's guards before it gets its VALIDATION_ERROR, on the live transports and in TestClient. An unauthenticated caller now gets AUTHENTICATION_ERROR rather than a schema error, invalid calls count against rate limits, and AuditMiddleware records them (status: "error", no args). Middleware sees ctx.state["_tool_arguments"] == {} for such a call; the handler, dependency injection, CacheMiddleware and ApprovalGateMiddleware never see the rejected input (no cache lookup, no approval request). Resource templates and prompts follow the same order.
  • build_agent(servers=...) is optional -- it defaults to None, the same as servers={}: an agent with no MCP tools (just the model, or extra_tools).
  • build_agent() no longer prints to stdout when an agent has no tools -- with no servers configured nothing is reported (a DEBUG log record on promptise.agent); when servers are configured but none of them offers a tool, a WARNING naming the servers is logged on promptise.agent instead of the old print.
  • Health cooldown is per anomaly, not per anomaly type -- see Fixed. A process whose agent gets stuck on several different calls now reports each (one health.anomaly event each, and the on_anomaly action for the run), where it reported only the first within cooldown. Repeats of the same anomaly are still reported once per cooldown, also across runs.
  • Open mode: create_tool and connect_mcp_server tell the model the change applies from its next run -- create_tool now answers Tool 'x' created. You can call it from your next run, not in this one: this run keeps the tools it started with, so finish the current task without it. (it was Tool 'x' created. Rebuild successful), and connect_mcp_server answers Connected to MCP server 'x' at <url>. Its tools are available from your next run, not in this one: ... (it ended with Rebuild successful). Both tool descriptions say the same, and create_tool's description no longer says run(**kwargs) -> str (declared parameters are passed to run as keyword arguments).

Documentation

  • docs/core/engine-prebuilts.md, reasoning-patterns.md, engine-nodes.md: the ledger "cuts redundant tool calls at equal accuracy" claim is now conditional on the model, with the guide's measurements (gpt-4.1-mini re-read records under 1.3.3 compaction; gpt-5-mini did not).
  • docs/core/tool-optimization.md: new "The tool summary in the system prompt" section with token measurements.
  • docs/core/engine-hooks.md: BudgetHook is per run, may overshoot by one node, and raises GraphBudgetExceededError mid tool loop.

Full changelog: https://github.com/promptise-com/Foundry/blob/main/CHANGELOG.md