Repository navigation
Releases: jhd3197/Prompture
Release list
v1.15.1
Changes
OpenAI's gpt-6 models refuse function tools on Chat Completions, so any agent or tool call on them failed with a 400. The OpenAI drivers now pick the right API for the model on their own.
Highlights
- Tool calls on gpt-6 models (Astra, Sol, Luna) work again. gpt-6 and later go over the Responses API by default, and older models stay on Chat Completions.
- Passing
api=to the OpenAI drivers still picks the API explicitly. - After each release,
mainis merged back intodev, sodevno longer falls behind the latest version.
Technical changes
OpenAIDriver/AsyncOpenAIDriver:apidefaults toNoneand resolves through the newdefault_openai_api(model). It reads the GPT generation from the model name (the same regex as the tokens-param guess), and N >= 6 meansresponses. An unknownapistill raisesValueError. (#123)reasoning_effort="none"is no workaround: gpt-6-astra and gpt-6.1-sol only acceptlow/medium/high/xhigh.publish.yml: newsync-devjob afterreleasefast-forwards (or merges)devtomainand pushes withGITHUB_TOKEN, so no dev build is triggered. (#124)- Tests:
TestDefaultApiintests/test_openai_responses.py.
Details
v1.15.0
Changes
Several of Prompture's prompts had been saying "do the thing" and hoping for the best. This PR rewrites the prompts the library ships so each one says what to return, how it can fail and what to treat as data. It also fixes the review loop, router and debate judge, which read free-text replies too loosely.
Highlights
- The review loop no longer approves a review that says "not approved". Reviewers now see the original task and end with an explicit verdict.
- The router no longer sends a request to the wrong specialist just because two agent names share a word. It can also answer "none" and fall back cleanly.
- Debates report a winner you can read in code, alongside the judge's written verdict.
- Text you extract from, retrieved RAG passages and TOON data are fenced off from the instructions. Instructions hidden in a document are much less likely to hijack the run.
- DeepAgent remembers more after compacting a long run: the user's exact limits, what was ruled out and what to do next. The summarizer prompt and the base prompt can now be replaced or dropped.
- DeepAgent's final answers lead with anything that failed or was skipped. Its sub-agents get full briefs and report back in a usable shape.
- Malformed JSON repair now knows the target schema and the parse error, so the repaired object is more likely to validate on the first try.
Technical changes
Verdict parsing (behavior fixes)
review_loop._default_approvereads the lastVERDICT: APPROVED | CHANGES REQUESTEDline. Without one, it falls back to an "approved" that isn't negated (not,isn't,never, ...) within the same sentence. Before, any review containing the substring "approved" counted.- New
_default_review_prompt(code, task)shows the reviewer the task and the work inside<task>/<work>tags and asks for the verdict line.AsyncReviewLooppasses the original prompt only when the default is in use, so customreview_promptcallables keep their one-argument signature. groups._match_agent_namereplaces both routers'_fuzzy_match_namelogic:- It accepts the bare name, ignoring quotes and punctuation, or exactly one whole-word mention.
- The longest name wins when one contains another.
NONE, or a reply naming several agents, returnsNone.- The word-overlap fallback that misrouted
data_analystvsdata_engineeris gone.
_DEFAULT_ROUTING_PROMPTputs the request in<request>tags and offersNONE.AsyncRouterAgentnow imports the prompt instead of keeping its own copy.DebateConfig.judge_prompt_template:- It asks for strongest points, weaknesses and a verdict, ending in
WINNER: <name>orWINNER: TIE. parse_judge_winnermaps the line to a debater name, or to the agent holding a position label such asFOR.- The result goes into the new
DebateResult.judge_winner.
- It asks for strongest points, weaknesses and a verdict, ending in
- Debaters after round one are asked to answer the strongest opposing points instead of repeating themselves.
Untrusted input
extraction.core.wrap_untrusted(text, tag, what)puts text inside tags, defuses a closing tag that appears in the text, and states that the content is data, not instructions.- The sync and async
extract_and_jsonifyandmanual_extract_and_jsonifypaths use it. Before, instruction and text were joined with a single space. Cache keys are unchanged. extract_from_data/extract_from_pandaswrap TOON data in<data>and prependTOON_DATA_NOTE, a one-line explanation of the format.render_outputlabels its format hint "Output format:" instead of a fake "SYSTEM INSTRUCTION:" inside the user turn.rag.pipeline.DEFAULT_RAG_PROMPTputs retrieved passages inside<context>tags with the same data-not-instructions note.- The web tool descriptions (
web_search,web_fetch,read_url, andWebSearchTool.DEFAULT_DESCRIPTION) also say that fetched content is data.
DeepAgent
SUMMARIZER_SYSTEM_PROMPTrewritten:- It lists what survives compaction.
- It treats only
[user]messages as the user's words and quotes limits exactly. - It folds earlier summaries in, points to files instead of copying them, and ends with a
## Next steptied to the latest request.
format_summarizer_inputappends the virtual-filesystem paths and the current todo list, which the agent cannot read back after compaction._stringify_messagelabels an earlier summary[earlier summary]so it isn't mistaken for user text.- New
summarizer_prompt=onDeepAgent,AsyncDeepAgentand bothcreate_*factories, passed through toSummarizationMiddleware/AsyncSummarizationMiddlewareassystem_prompt. BASE_DEEP_AGENT_PROMPTgains:- a check before irreversible or outward-facing tool calls;
- a final-answer contract: report only what tool results show, lead with failures, and never present partial work as done.
- New
base_prompt=replaces the base section;""drops it, so a persona can set the role alone.assemble_system_prompttakes the same argument. TASK_TOOL_DESC_TEMPLATEandSUBAGENT_PROMPT_TEMPLATEask for a full brief (goal, what is known or ruled out, exact inputs, what to return) and for findings rather than delegated decisions.- New
SUBAGENT_REPORT_NOTEis added to every sub-agent system prompt by_resolve_subagent_prompt. It says the final message is all the parent sees, to lead with the answer, flag what couldn't be verified, and state assumptions.
Tool descriptions
- Virtual-filesystem tool descriptions rewritten around use and failure modes:
- read_file: what the line-number prefix is and how
offsetpaging works. - edit_file: unique matching,
replace_allfor renames, emptynew_stringto delete. - glob: fnmatch
*also matches/. - grep: Python
resyntax, the three output modes and(?i).
- read_file: what the line-number prefix is and how
- Parameter descriptions tightened to match.
edit_filedetects copiedread_fileline-number prefixes inold_stringand says how to fix it. A plain miss suggests re-reading the file.search_tools/load_toolssay to check deferred tools before declaring something impossible, and to load a tool before its first call instead of guessing its arguments.- All built-in descriptions stay within
MAX_TOOL_DESCRIPTION_CHARS.
JSON repair and evaluation
- New
json_repair_prompt(text, json_schema=, error=)shared by the sync and asyncclean_json_text_with_ai. Both gain keyword-onlyjson_schemaanderror. ask_for_jsonand bothConversation.ask_for_jsonvariants pass the schema and the parser error to the repair call.- The validation-retry note asks for the full corrected object, using null instead of invented values.
DEFAULT_VERIFICATION_PROMPTineval.faithfulnessasks for aQUOTE:line of deciding evidence before the label._parse_verdicttakes the last label, so a quote containing a label word can't decide the verdict. A SUPPORTED or CONTRADICTED verdict withQUOTE: NONEis downgraded to SILENT. A bare label still parses as before.
Details
v1.14.0
Changes
Adds Gemini 4 Argon on both the Gemini API and Vertex AI, and fixes two failures that hit long-running agent work: Claude requests that generate for more than a minute were dropped by the network, and a ParallelGroup nested inside an async group could not run at all. It also stops a second companion process from starting next to a running one.
Highlights
google/gemini-4-argonandgoogle_vertexai/gemini-4-argonwork in sync, async, streaming, tool and live conversations, withlow,mediumandhigheffort and Argon's pricing, including its scheduled price changes.- Long Claude generations (a full page, a long stylesheet, a big extraction) no longer fail with
APIConnectionErrorafter a minute: requests above 4096max_tokensnow travel over the streaming API and return the same result as before. - A
ParallelGroupinside anAsyncSequentialGrouporAsyncLoopGroupnow runs its agents instead of failing with "asyncio.run() cannot be called from a running event loop". - Only one companion runs per user: a second
prompture companionstart finds the lock and leaves the running one alone.
Technical changes
- Gemini: a shared
drivers/_google_genai.pynow holds the request, response and part handling the four Google drivers (sync and async, Gemini API and Vertex AI) used to duplicate. Signed response parts (_google_parts) are preserved across turns inConversation/AsyncConversationand live events, so thought signatures survive tool loops. - Gemini usage counts thinking and cached tokens.
_effort.google_thinking_levelis now model-aware: Argon gets a realmedium, older models keep the low/high mapping, and an explicitthinking_budgetis never overridden. infra/pricing.py: local rate entries can carry acost_schedule, evaluated at lookup time so a long-running process crosses a price change;rates/google.jsonandrates/google_vertexai.jsonadd Argon. Google SDK requirements and the README's Argon section are updated.companion/server.py:instance_lock()takes an OS-level lock (msvcrton Windows,fcntlelsewhere) on~/.prompture/companion.lockfor the life of the process, so a crash never leaves it stale and a slow companion is never mistaken for a missing one.groups/async_groups.py:ParallelGroup.run()returns therun_async()coroutine when an event loop is already running (which is how every parent async group calls it) and still runs to completion withasyncio.run()from plain sync code.drivers/claude_driver.py/async_claude_driver.py: new_create_message/_acreate_messagehelpers used bygenerate_messages(plain and JSON mode) andgenerate_messages_with_tools. AboveSTREAM_TRANSPORT_ABOVE_MAX_TOKENS(4096) they callmessages.stream()andget_final_message(); below it they keepmessages.create(). Thestream_transportoption forces either path. This also lifts the SDK's refusal of non-streaming requests whosemax_tokenscould run past ten minutes.devis brought level withmain(the release merges since 1.13.1 and the image-block preparation fix from #118) so this merge carries no conflicts.
Details
v1.13.11
Changes
The game harness used to treat every failure the same way and play a random move when it ran out of retries, which made it hard to say why a game went the way it did. This PR gives each turn a full record, a deadline, a budget and a stop button. It adds a reproducible way to measure play, line-bound plan memory, sharper chess hints, resumable tournaments and opt-in experiments. Alongside that, providers gain one shared reasoning-effort option, credentials take effect without a restart, and Claude Code / Codex logins can be used as models.
Highlights
- Inspectable turns. Every move records each model call: prompt, reply, why a reply was rejected, timing and cost. Unknown prices show as a lower bound instead of $0.
- Turns can stop. Turns can have a deadline, a Stop button and a spending budget. A stopped turn never plays a move, and a reply that arrives late is ignored.
- Clear failure handling. Running out of retries can rescue (marked), forfeit or stop the game. Bad credentials stop immediately instead of producing random moves.
- Measured evaluation. Validated chess, Connect Four and Othello positions produce scorecards that keep strength, reliability and cost apart. Comparisons refuse to call scripted runs or unfair setups an improvement.
- Sharper chess hints. Warnings for mate-in-one replies, x-ray and pinned-defender exchanges, and promotions the opponent can force. Forks that are simply traded off are no longer reported.
- Memory and prompt budgets. Each side remembers its own plans on the same game line only, with checkpoints for resumed games. A prompt-size budget never drops legal moves.
- Tournaments. Side-balanced round robins resume after a stop without duplicate results, run with bounded concurrency and a shared budget, and export leaderboard eligibility flags.
- Opt-in experiments. A one-shot review of warned moves, two proposals plus a selector, and chess board images, all off by default.
- Providers. One
effortoption works across providers. Saved keys apply immediately, and local Ollama/LM Studio servers can be probed.claude-code/<model>andcodex/<model>run on a subscription login.
Technical changes
Turn records and failures (prompture/games/records.py, trace.py, base.py)
Attemptgains id, purpose, rendered prompt/schema, public or partial response, validation,FailureInfo, timing, finish reason, and per-call usage withusage_status/cost_status. Attempts are validated before they are traced.MoveDecisiongainsrecord(TurnRecord),assistanceandmodel_selected; existing positional constructors are unchanged.classify_failuremapsresilience.errors.classify_errorontoFailureKind. Status codes,Retry-Afterand cause chains are preserved, and text matching is only a last resort.Accountingcounts partial, unknown and abandoned calls.source_metareads the driver's own meta, so extraction's defaultcost: 0.0is not taken as an observed price.- New
failure_policy(rescue/forfeit/stop). Auth/configuration failures, cancellation and budget exhaustion raiseTurnFailed/TurnCancelledinstead of rescuing. fingerprint()hashes behavior settings (versions, level, concepts, memory, retry/failure policy, allowlisted options, engine identity, strategy, representation). Model IDs, names and secrets are excluded.- Traces:
JsonlTraceSink/MemoryTraceSinkwith allowlisted fields, credential redaction, a per-record byte bound, torn-line tolerantread_jsonl_trace, and a warn/raise sink-failure policy. - Extraction:
ExtractionError.raw_text/stop_reason, streampartial_text, andask_for_json(on_delta=..., should_stop=...)(StreamStoppedcloses the stream). Empty replies cut off bymax_tokenskeep their usage and say why.
Turn control (prompture/games/control.py)
deadlinecovers requests, retries and backoff. With a deadline orCancelToken, requests run on daemon worker threads. A stopped stream closes between chunks. A blocking call is markedabandoned, and its late result is only logged (late_response); no request is sent on its behalf.- Completion/cancel races are rechecked before a move is returned.
achoosecancels the turn when its task is cancelled. RetryPolicygives transient failures a separate allowance with exponential backoff, jitter and cappedRetry-After; legacy behavior is kept when it is unset.GameBudgetreserves before every request and settles afterwards (calls/tokens/cost/seconds, thread-safe, shareable). An unknown price stops cost-only budgets.TurnEventsequencing plus a bounded, delta-coalescingEventBuffer. Handler exceptions are isolated, and driver callbacks still fire.- OpenAI chat-completions and Ollama streams now close their HTTP response in
finally, so an early-closed generator releases the connection.
Evaluation (evaluation.py, fixtures.py, chess/fixtures.py)
Fixture(provenance, dev/validation/test split, initial state + move history, acceptable moves or a named evaluator, hint expectations with known-gap markers). States replay through each game'sload_state, and illegal histories raiseFixtureError.check_hintsruns with a no-call driver.evaluateruns in seeded random order with fresh players per sample and rawSamplerecords.scorecardshows numerators/denominators and amissinglist.summary_text,save_run/load_run(versioned),paired_schedule.comparewithPreregistration: paired bootstrap, reliability and cost non-inferiority gates, and refusal verdicts (not_evidence,not_comparable,not_preregistered).- 33 chess positions (mates, quiet defense, forks, exchanges, promotions, en passant, repetition, sacrifices, three saved regressions) with a
checkmateevaluator.PuzzleReport.scorecard().
Chess tactics (chess/exchange.py, chess/motifs.py)
- Bounded static exchange count over legal recaptures (pins excluded, x-rays included, promotion values), used by
losing_capture, safety, material-win andallowshints. allowsflags mate-in-one replies and opponent queen promotions the mover cannot win back, ignores forks met by an even trade, and reports when its reply cap is reached. Underpromotions are named.Motifgainsseverity,exactand anevidencewitness line;HINTS_VERSIONis 3.
Memory and prompts (memory.py, base.py)
DecisionMemory(none/previous/window) keyed byGameLine. Decisions stay pending until the line shows the move applied, orcommit()is called. Rescues carry no plan. Versioned checkpoints validate game, root and move prefix.ChessHarnessreplaces its private one-turn memory withmemory="previous".context_budgetkeeps the side, rules, rendered position, every candidate and the instructions, then adds notes urgent → warning → memory → info. When required content cannot fit, the turn fails with a configuration error.- Games gain
history(),dump_state/load_stateandsides. Connect Four and Othello set-up boards become their own root, and Othello passes are recorded as plies.
Batches (tournament.py, match.py)
Entrant/MatchContext,pair_schedule,double_round_robinovergame.sidesand start states, and a stableschedule_id.run_batch: bounded concurrency, one admission slot per provider per game, shared budget, cancel stops admission.BatchStoreJSONL with idempotent game IDs. Interrupted attempts are recorded with unknown usage before replay. A crashing entrant ends only its own game, which keeps the spend of its completed turns.GameOutcome.eligibility(),standings()over decided games, and exploratorycalibratewith minimum evidence and game/time/budget ceilings.play_matchaddsterminal_reason,forfeited_by,error,game_id,cancel, andstart_game/commithooks.
Experiments (strategies.py, chess/render.py)
Reconsider(exactly one review of a warned choice, keep/stop on review failure) andProposals(single-call workers plus a selector constrained to the proposed moves, partial-failure rules). Both run through the harness request path.representation="text+image"withRenderedImageand deterministicchess.svgboards (PNG through optionalcairosvg). Text fallbacks are recorded with a reason.- Scorecards count review fixes and harms, proposal agreement, and image turns versus fallbacks.
experiment_statusreports live-pending without live evidence.
Providers (drivers/_effort.py, infra/, coding_agent_driver.py)
options["effort"]is mapped per API family (Anthropicoutput_config.effort, OpenAI-stylereasoning_effort, OpenRouterreasoning.effort, Geminithinking_level) across sync and async drivers.- Claude streaming supports schema-enforced JSON mode (structured output or a forced
extract_jsontool) with cache breakpoints budgeted for the tool. OpenAI forwards a per-requesttimeout. CredentialStorewrites refresh changed settings and clear discovery caches in-process. Windows junctions are detected on Python < 3.12.- New
provider_keys(key catalogue and live validation, shared withprompture setup) andlocal_servers(parallel probes of Ollama/LM Studio model lists). claude-code/<model>andcodex/<model>drivers (sync/async, plugin-registered) run the signed-in CLI in an empty temp directory.costis 0 with the CLI's estimate kept inreported_cost.
Docs and examples
docs/GAMES.mdcovers the APIs and the host integration contract.examples/game_progress_example.py,game_evaluation_example.pyandgame_tournament_example.pyuse local drivers or engines unless--modelis passed.
Details
v1.13.10
Changes
Merges everything on dev into main: driver fixes, per-call API keys, and three new features (brand context for marketing work, assistants defined in files, and a tested feedback loop).
Drivers and extraction
- Claude on the 5.x models. Fable 5.1, Mythos 5.1, Opus 5.5 and Sonnet 5.5 reject a forced
tool_choice, so every JSON-mode call to them returned a 400. They now get structured output (output_config.format) instead. The driver no longer sendstemperatureto those families, and theeffortoption reachesoutput_config.effort. ANTHROPIC_API_KEYnow works as a fallback whenCLAUDE_API_KEYis unset.- Per-call
api_key.extract_with_modelandextract_and_jsonifyaccept one, so an app can use each user's own key. - Null metrics no longer fail a generation. Debug logs formatted meta values with
%d/%f, and a provider reportingnullraisedTypeErrorout of a rotating file handler.
New: prompture.marketing
BrandContextholds the customer, pains and objections, core messaging, offers, voice and banned words, verified proof and testimonials, contact details and brand colors.- It loads from YAML, JSON or a fill-in Markdown file (
BrandContext.save_markdown_template()writes the blank one). - Pass it as
brand=toAgent,AsyncAgent,Assistantandextract_with_model. It goes into the system prompt with rules against inventing numbers, testimonials or contact details.
- It loads from YAML, JSON or a fill-in Markdown file (
ClaimsCheckerflags URLs, emails and phone numbers the brand doesn't list, plus banned words. With a model, it also checks each factual claim against the proof points throughFaithfulnessEvaluator.- Output models whose limits are validators, so extraction retries on the exact rule that broke:
SocialPostSet,ShotList/VideoClip,EmailSequence,CoreMessaging,IdealCustomer,TopTens,LeadMagnetIdeas,OfferStack,ScorecardAnalysis.- Example limits: 5-word image hook, no text in image prompts, voiceover sized to the clip length, email send days in order.
marketing.generate()/agenerate()create any of those models for a task, with the brand applied.analyze_metric()does the scorecard arithmetic without a model.
New: Assistant.from_file()
Loads an assistant from YAML, JSON or Markdown (front matter plus body). A file can set instructions, knowledge documents (inlined, with PDF/DOCX through ingestion and a 200k-character cap), conversation starters, named tools, skills and a brand. with_instructions() swaps in a new version of the instructions.
New: FeedbackLoop (prompture.execution.improve)
FeedbackLogrecords user corrections. They are summarized into rules in a## Feedback rulessection, without touching the rest of the instructions.- Old and new instructions are scored with an
LLMJudgewhose rubric includes every rule. - The change is promoted through
PromotionLedgeronly if it gains at least 0.01 on held-out prompts.rollback()restores the previous version.
Tests
- 67 new tests cover the three features. All model calls are stubbed.
tests/test_reasoning.py: three driver stubs now accept theapi_keykeyword. Without this, thedevrun on97bcbb9failed.- Locally:
ruff check .andbandit -r prompture/pass, and pytest gives 5,364 passed, 89 skipped.
Details
v1.13.9
Changes
What broke
A turn that carried an image failed on these paths with Object of type ImageContent is not JSON serializable. They passed the universal {"type": "image", "source": ImageContent} blocks straight to the provider SDK instead of running _prepare_messages first:
OpenAIDriver/AsyncOpenAIDriver:generate_messages_with_tools_stream(chat-completions branch)GroqDriver/AsyncGroqDriver:generate_messages_with_tools_streamClaudeDriver/AsyncClaudeDriver:generate_messages_with_tools,generate_messages_stream,generate_messages_with_tools_stream
So Agent.run_live(..., images=...) and AsyncConversation.ask_live with images could not send a picture to OpenAI, Groq or Claude.
The fix
- Each of those paths now converts messages with the driver's
_prepare_messages, asgenerate_messagesand the Responses API branch already did. _prepare_claude_vision_messagespasses through a block that is already in Anthropic's shape (itssourcea plain dict), so preparing twice is harmless. The async Claude_do_generatenow prepares as well.
Tests
tests/test_vision.py adds checks that the OpenAI tool stream (sync and async), the async Claude tools / stream / tools-stream paths and the sync Claude streams all hand the SDK converted image blocks, plus a double-prepare check for the Claude helper. The vision, driver, streaming and live-event suites pass (250 passed, 1 skipped).
Details
v1.13.8
Changes
Follow-ups from running prompture delegate for real, end to end. A stuck queue no longer blocks every later delegation, and agents that install the Prompture skill now learn to reach for delegation before an external API.
Highlights
prompture delegate-stopabandons a stuck or unwanted queue: the running step is cancelled, the run stays in history as stopped, and the nextprompture delegatecan start right away. The 409 "already running a queue" message now points at it.- The bundled agent skill (
prompture skill install) teaches delegation: before calling an external LLM/translation API or hunting for a key, runprompture delegateinstead — with when not to delegate, the exit-code table and the answer/resume/stop loop. prompture skill installnow also targets Kimi Code (~/.kimi-code/skills) and the shared~/.agents/skillsconvention, not just~/.claude/skills— one command covers the agents on a machine.- Verified live: a real delegated run through Claude Code completed a translation task unattended and wrote the output file (10.8s, $0.67 API-equivalent), driven entirely by these commands.
Technical changes
Queue abandonment (prompture/cli/delegate_cmd.py)
delegate-stopposts/v1/automations/current/stopthrough the same loopback-only client the other delegate commands use; without a queue it surfaces the companion's 409 detail.- Found by live testing: a queue paused on failure made every later
delegatefail with 409 and no way out short of a companion app. The 409 message now names bothdelegate-statusanddelegate-stop. - README's "Delegating a task" section lists the new command.
Skill targets (prompture/skill/__init__.py)
TARGETSgainskimi(~/.kimi-code/skills/prompture) andagents(~/.agents/skills/prompture); marker-file ownership and uninstall refusals are unchanged, so foreign directories are still never touched.prompture/cli/skill_cmd.pyexposes both in--targetwith updated help.
Skill content (prompture/skill/data/SKILL.md)
- Frontmatter description adds the delegation trigger so agents match the skill when about to call an external API for a self-contained task.
- New "Delegating tasks" section plus a routing-table row: delegate vs. inline judgment (don't spawn an agent for trivial work or anything needing the current conversation), exit codes
0/1/3/4and what to run for each,--json,--no-wait,--agent,--model,--cost-cap,--timeout.
Tests
tests/test_cli_delegate.py: stopping a stuck queue frees it for a fresh run, stopping with no queue errors cleanly (18 tests in the file, all run against a realCompanionServerwith a fake agent runner).tests/test_skill_install.py: install/uninstall round-trips for thekimiandagentstargets.
Details
v1.13.7
Changes
Left to their own devices, small models will happily play Qxh9. This PR gives any model a harness for chess, Connect Four and Othello where illegal moves can't happen, with dials for how much help it gets. It also lets scripts and other agents hand work to the companion's coding-agent queue without their own API keys, and keeps long agent runs from drowning in oversized tool results and tool schemas.
Highlights
- Any model can play chess, Connect Four or Othello and only ever play legal moves: it picks from a menu, a bad answer is sent back with the reason, and a move it never gets right is played for it and counted.
- A help level runs from "every legal move" up to "only the engine's best move", and concept switches (forks, pins, double threats, corners ...) decide which ideas a model is told about, so a bot can know forks but not skewers.
- Connect Four and Othello come with built-in engines, so every level works with nothing to install; chess uses Stockfish for the engine levels.
- Full games between models, engines and random players, and chess puzzle runs that measure whether teaching a concept helps a model.
prompture delegate "..."hands a task to Claude Code or Codex through the companion, which starts itself if needed;delegate-status,delegate-answeranddelegate-resumefollow up, and exit codes tell scripts how the run ended.- Agents keep large tool results instead of cutting them off: the model gets a preview and reads the rest on demand, and very large tool sets are searched and loaded as needed instead of all being sent every turn.
- Deep agents now pick when to summarise from the model's context window, and the summary keeps user instructions, findings and the current step in fixed sections.
Technical changes
Game harnesses (prompture.games)
games/base.py:GameHarnessoffers candidate moves, puts their names in the reply schema as anenum(enforced natively by drivers with schema support, such as Ollama'sformat), validates replies throughGame.parse_move, retries with the rejection reason, and falls back to the engine's best or a random move withMoveDecision.rescued=True. Usage is summed across attempts.level(0-5) controls which moves are offered (all, tagged, blunders removed by ablunder_dropin winning chances, engine top-N unranked, top-N ranked with scores, best only);conceptsfilters which motif tags are attached. Engine levels useGame.default_engine()when no engine is passed.- A failed model call (timeout, backend down) is recorded as a failed attempt instead of raising, so a match survives a flaky backend and the rescue shows up in the report.
GameandEngineprotocols, plusMotif,Score,Candidate,Attempt,MoveDecisionandcheck_concepts, are the extension points for new games.games/match.py:play_match(game, players, state=None, max_moves=400)returns aMatchResultwith the winner, moves, per-side rescues and afinishedflag;EnginePlayerandRandomPlayerimplement the samechoose(state)interface as a harness.games/connect4.py:Connect4Board(flat cells, play/undo,winning_cells,immediate_wins), tags forwin,block,threat,double-threat,safetyandcenter, a prompt with a grid plus per-column stacks, andConnect4Engine, a centre-first alpha-beta search over open-line windows (default depth 6) with "wins in N" / "loses in N" labels.games/othello.py:OthelloBoardwith flips and forced passes (PASS), tags forcorner,x-square,edge,mobilityandsafety(newly conceded corners), andOthelloEngine, alpha-beta on a positional weight table plus mobility (default depth 4).games/chess/motifs.py:describe_movetags facts (capture, check, promotion, castling) and concepts (mate,back-rank-mate,fork,pinincluding capturing a pinned piece,skewer,discovered-attack,hanging-piece,safety), using one-move static checks; a piece lost on the spot cannot fork, pin or skewer, and pawn pins and pawn discoveries are not reported.games/chess/harness.py:ChessGame(SAN names, SAN/UCI parsing, FEN plus piece lists in the prompt),ChessEnginewrapping a UCI engine path orSimpleEngineinto Lichess winning-chance scores, andChessHarnessas aGameHarnesssubclass.games/chess/puzzles.py:load_puzzles,play_puzzleandrun_puzzlesread Lichess-format packs (opponent's move first, any mate accepted on the last move) into aPuzzleReportwith solve rate and rescues.- New
chessextra (chess>=1.10), included inall;prompture.games.chessraises a clear install hint without it.
Task delegation (prompture/cli/delegate_cmd.py)
delegate,delegate-answer,delegate-resumeanddelegate-statusdrive the companion's automation queue over its loopback API with the bearer token from~/.prompture/companion.json, through a stdliburllibclient.- The client refuses any companion URL that is not
127.0.0.1orlocalhost, so the bearer token never leaves the machine, matching the hook client. - A missing companion is started detached (
DETACHED_PROCESSon Windows, a new session elsewhere), then the command waits for its state file to answer. - Step logs stream to stdout by polling; exit codes are
0finished,1failed,3waiting for an answer,4stopped or detached.--json,--no-wait,--timeout,--agent,--modeland--cost-capare supported. - Registered through
_COMMAND_MODULESincli.py; documented in the README.
Tool-context management (prompture/agents/tool_context.py)
ToolResultPolicyandshape_tool_resultsmove results overper_call_tokens(default 4000), or the largest results of a round overbatch_tokens(default 12000), into anArtifactStore, leaving a head/tail preview and anartifact://handle;read_tool_result(handle, query, offset)is registered only once something is stored. Error results are cut, never offloaded;offload=Falserestores plain truncation.ToolLoadersendssearch_tools/load_toolsinstead of the full schemas when they would exceed ~8k tokens (defer_tools="auto"), keeps loaded tools for the rest of the conversation, and always sendspreload_tools.repair_tool_pairsgives every assistant tool call a result (a placeholder for interrupted calls) and drops orphaned results, so trimmed or interrupted histories are not rejected by providers.ConversationandAsyncConversationgain this throughToolContextMixin;AgentandAsyncAgentaccepttool_result_policy,defer_toolsandpreload_toolsand pass them through.ArtifactStoregainstext(handle, scope=...), a scope-checked text read, andclear(scope=...).create_deep_agentand its async twin default tosummarize_at_tokens="auto", which resolves viaresolve_summarize_thresholdto the smallest of 80k, 75% of the model's context window, and the window minus the output reservation (frommax_output_optionor the model's limit), with a 2k floor and a fallback to 80k for unknown models.SUMMARIZER_SYSTEM_PROMPTrewritten into fixed sections (task, user messages, findings, actions, errors, open items, current step) that keep exact values andartifact://handles.
Details
v1.13.6
Changes
Some sites answer a fetch with little more than "please use a modern browser", and until now that politely went into the cache as if it were the page. This PR treats those app shells as a failed fetch so the next backend gets a turn, and adds an AniList reader that gets anime and manga data from the site's public API instead of its client-side pages.
Highlights
- Pages that only render with JavaScript no longer come back as a useless "requires a modern browser" result; the next fetch backend is tried and nothing empty gets cached
- AniList title and character links now return the cast: each character's role, gender, age, description and voice actors
search_anilistfinds an anime or manga by title so its cast can be read in one more step, including titles typed with accents or long-vowel marks
Technical changes
App shell detection
- New
is_js_shell_page()inprompture/capabilities/challenge.py, exported fromprompture.capabilities. It looks for "needs JavaScript / a newer browser" markers in the first 1,000 characters only, so an article that mentions JavaScript further down still counts as content JinaReaderBackendandDirectBackendraiseRequestRejectedErroron a shell. The fetch chain moves to the next backend, and since failures are never cached, a shell can't be stored for a page's full lifetime
AniList reader
- New
prompture/tools/web/readers/anilist.pywithAniListReader, registered among the built-in readers after Wikipedia. It handlesanilist.coanime, manga and character URLs by ID and queries the public GraphQL API atgraphql.anilist.co. Requests always go to that fixed host; the URL only supplies the ID - Character entries include role, gender, age, a cleaned description (spoilers removed by default) and voice actors. API errors map to
RequestRejectedError/BackendUnavailableErrorso they fail over like the other readers search_anilist()is exported fromprompture.tools.webto go from a title to its page. AniList's search matches romanized titles without macrons but returns unrelated entries with them, sofold_title()strips accents from Latin letters first ("Shippūden" → "Shippuden") and the query as given is tried only when that finds nothing; marks on other scripts, such as kana dakuten, are kept- AniList results use the 1-day cache lifetime, the same as Wikipedia
- README reader lists and the cache lifetime table mention AniList
Details
v1.13.5
Changes
Prompture could talk to almost any model, but an agent built on it still had to bring its own way to search, read a page or watch a video, and nobody could easily tell which of those actually worked on a given machine. This PR adds that capability layer: web search, page and URL readers, transcription, an MCP hub, wrapped CLI tools, domain packs and a research agent, all built on a shared foundation of ordered backend chains that fail over instead of failing. prompture doctor reports what works, which backend is active and the exact fix for anything that doesn't. It also fixes GPT-6 models (including gpt-6-astra) being rejected, and Ollama ignoring max_tokens.
Highlights
- Web search, page fetching and URL readers work with no API keys at all; adding keys raises quality and limits, and every result says which backend served it and whether it fell back
read_urlunderstands YouTube, GitHub, Hacker News, arXiv, Wikipedia, RSS/Atom feeds and podcasts, and falls back to a plain page fetch for anything else- Repeat searches and static pages come back instantly from a local cache, even in a new process; weather, prices and news expire in minutes, papers and transcripts in days, and cached results say how old they are
prompture transcribeandsummarize_mediaturn a video, podcast or audio file into a timestamped transcript and a summary with key points; audio is never sent to a second provider without opting inprompture research "question"produces a cited report from pages it actually opened, within fetch, token, cost and time budgets- Agents mount tools by name:
tools=["web:all", "mcp:exa", "pack:finance", "cli:gh"] prompture mcp add --preset exaregisters MCP servers once and reuses them by name; secrets are stored only as${ENV}referencesprompture doctorshows every provider, tool, media piece, MCP server and binary with its status, active backend and fix;prompture setup/configurestore keys in an owner-only file with profilesprompture skill installgives coding agents a skill describing all of this, andprompture check-update/watchreport new releases- GPT-6 models (
gpt-6-astra,gpt-6-luna,gpt-6.1-sol) no longer fail with "Unsupported parameter: max_tokens", and Ollama now honorsmax_tokens, temperature and the other sampling controls
Technical changes
Capability foundation (prompture/capabilities/)
BackendChaintriesBackends in order, skips unavailable ones and classifies failures throughclassify_error: transient errors retry once, auth/quota/rate-limit/challenge errors move to the next backend, and fatal errors re-raise with the attempts attached asexc.attempts. Results areChainResults withroute = {served_by, fallback, attempts}- An env override (e.g.
PROMPTURE_SEARCH_PROVIDERS) moves named backends to the front and keeps the rest in place after them; unknown names are ignored so a stale value can't hide working backends.only=restricts the chain classify_backend_errorpinsUnsafeURLError(fatal),ChallengePageErrorandResponseTooLargeError(failover) to fixed actions, soreset_error_rules()can't turn an unsafe URL into a failoverprobe_commandruns a side-effect-free command with a UTF-8 child env and stripped interpreter variables and classifiesok | missing | broken | timeout | error; exit 126/127, exec failures and "No Python at" style output count asbroken.cached_probeshares results for 60 sHealthStatusand a capability registry (register_capability,check_capabilities) that doctor walks; a crashing check becomes anerrorrow instead of breaking the reportnormalize_public_http_urlallows only publichttp(s): it blocks private, loopback, link-local, metadata, CGNAT, multicast and reserved ranges, IPv4-mapped / 6to4 / Teredo / NAT64 wrappers, decimal/octal/hex IPv4 spellings and internal hostnames, and checks every resolved address.PROMPTURE_WEB_ALLOW_PRIVATEis the escape hatchGuardedHTTPAdapterchecks the address each socket actually connected to before any request bytes are sent, closing the DNS-rebinding gap between validating a hostname and connecting to it.safe_getmounts it on its own session and on anyrequests.Sessionpassed in; connections to a configured proxy are exemptsafe_getfollows redirects manually and re-validates each hop, streams the body with a byte cap, detects challenge pages and raises typedHTTPStatusErrors that carry status and headers. Proxies come fromPROMPTURE_<BACKEND>_PROXYorPROMPTURE_PROXYis_challenge_pagescores combined Cloudflare / captcha markers in the first 4 KB, requiring more evidence on 2xx responsesscrub_url_credentials/scrub_secretsremove URL userinfo, bareuser:pass@host, secret-looking query/fragment parameters, known API key shapes and bearer tokens. They're applied toDriverHTTPErrormessages, every capability error and health row, and a newSecretScrubbingFilterinstalled byconfigure_logging, which also scrubs rendered tracebacks (exc_text) and stack infoprompture/tools/named.pyresolves"namespace:name"tool specs;Agent,AsyncAgentandDeepAgentaccept them next to callables andToolDefinitions
Web tools (prompture/tools/web/)
- Search backends
tavily,exa,serper,brave,jina,searxngand keylessexa_mcp(a minimal JSON-RPC client over streamable HTTP, nomcpdependency) behindweb_search/asearch, with domain filters, recency, URL dedupe and aSearchResponsecarrying the route web_fetchchainsjina_readeranddirect(safe_get+ stdlib HTML to Markdown,trafilaturawhen installed), validates the URL before any backend sees it, pages long content withstart=and caches for 10 minutesread_urlroutes to readers (YouTube transcript API, thenyt-dlpsubtitles, then transcription; GitHub REST, thengh; HN Firebase; arXiv API; Wikipedia REST; podcast enclosures; feeds), with per-reader step overrides viaPROMPTURE_READER_<NAME>_BACKENDSsearch_platformfor YouTube, GitHub (repositories, issues, code), Hacker News and arXiv;WebToolkitand theweb:namespace build agent tools that return strings and never raiseWebSearchToolkeeps its signature; with nothing configured it now falls back to keyless search instead of raising- New
prompture/tools/web/cache.py: search, fetch,read_urland platform search share a cache built on the existingMemoryCacheBackend/SQLiteCacheBackend(default SQLite at~/.prompture/cache/web_cache.db, 5,000 entries;PROMPTURE_WEB_CACHE=memory|off,PROMPTURE_WEB_CACHE_PATH). Lifetimes come fromttl_for: volatile queries (weather, prices, scores, news,recency_days <= 1) and fast-moving hosts 10 min, feeds 15 min, platform search 30 min, ordinary pages 1 h, other searches 6 h, Wikipedia and podcasts 1 day, papers, PDFs, DOIs, commit-pinned GitHub files and YouTube transcripts 7 days read_urlcaches the full reader result and pages from it, so walking a long transcript never refetches. Errors and empty results are never stored, values over 2 MB stay out of the disk cache, and hits carryroute["cached"]/cache_age_splus a "cached N min ago" footer. Every entry point takesuse_cache=andcache_ttl=;clear_web_cache(),cache_info()(read-only, never creates the database) and aweb_cachedoctor row round it out
Media understanding (prompture/media/understand/)
transcribeaccepts a URL or file, downloads direct audio throughsafe_getand video/podcast pages throughyt-dlp, then downmixes and chunks long media withffmpegand stitches the timestamps. It enforces caps on source size, chunk count and upload bytes, and cleans up temp files infinallyautopicks the first configured STT provider (Groq, OpenAI, ElevenLabs; reorder withPROMPTURE_STT_PROVIDERS); a second provider is used only withallow_provider_fallback=True- New
GroqSTTDriver, STT usage recorded throughrecord_media_usage,summarize_mediamap-reduce summaries with timestamped key points,transcribe_media/summarize_mediaagent tools, andprompture transcribe
Doctor and updates
prompture.doctor.check_all()returns aDoctorReportwith a versionedprompture.doctor/1JSON schema. The providers section checks key presence, base URLs, SDK importability and driver import offline, and--livelists models only, never generating anything paid. Unconfigured providers collapse into one rowprompture doctor,check-update(one PyPI call cached for 24 h, with changelog highlights) andwatch(exit 1 when something is broken, 2 for an update only with--fail-on-update)- The companion info payload gains a
healthsummary, cached and never blocking
Setup and credentials
CredentialStorewrites~/.prompture/credentials.yamlonly on first write, owner-only (0600, or a user-only ACL viaicaclson Windows), atomically, refusing symlinks and junctions, with a 1 MB read cap and profilesSettingsreads the store below env vars and.env;get_config_valuegives the same precedence to proxies and backend overridesdetect_environmenttells local machines from servers (container, CI, SSH, headless);prompture setup,configureandresetall support--dry-run, validate keys live and never echo them
MCP hub (prompture/mcp/hub.py)
- Named servers in
~/.prompture/mcp.jsonplus a project.prompture/mcp.jsonthat wins by name. Literal secrets are refused in favor of${ENV}references resolved at connect time resolve_mcp_toolsbacksmcp:<name>with pooled sessions on a background loop so sync agents can call them, per-server timeouts,<server>__<tool>prefixing and usage-ledger entries per callhttp_sessionfor streamable HTTP, presets (exa,deepwiki,github,fetch,time,git,memory,filesystem,sequential-thinking), ...