Skip to content

Releases: jhd3197/Prompture

v1.15.1

Choose a tag to compare

@github-actions github-actions released this 21 Oct 14:44
6e9f43e

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, main is merged back into dev, so dev no longer falls behind the latest version.
Technical changes
  • OpenAIDriver / AsyncOpenAIDriver: api defaults to None and resolves through the new default_openai_api(model). It reads the GPT generation from the model name (the same regex as the tokens-param guess), and N >= 6 means responses. An unknown api still raises ValueError. (#123)
  • reasoning_effort="none" is no workaround: gpt-6-astra and gpt-6.1-sol only accept low/medium/high/xhigh.
  • publish.yml: new sync-dev job after release fast-forwards (or merges) dev to main and pushes with GITHUB_TOKEN, so no dev build is triggered. (#124)
  • Tests: TestDefaultApi in tests/test_openai_responses.py.

Details

v1.15.0

Choose a tag to compare

@github-actions github-actions released this 21 Oct 03:50
e0aafd9

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_approve reads the last VERDICT: APPROVED | CHANGES REQUESTED line. 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. AsyncReviewLoop passes the original prompt only when the default is in use, so custom review_prompt callables keep their one-argument signature.
  • groups._match_agent_name replaces both routers' _fuzzy_match_name logic:
    • 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, returns None.
    • The word-overlap fallback that misrouted data_analyst vs data_engineer is gone.
  • _DEFAULT_ROUTING_PROMPT puts the request in <request> tags and offers NONE. AsyncRouterAgent now 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> or WINNER: TIE.
    • parse_judge_winner maps the line to a debater name, or to the agent holding a position label such as FOR.
    • The result goes into the new DebateResult.judge_winner.
  • 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_jsonify and manual_extract_and_jsonify paths use it. Before, instruction and text were joined with a single space. Cache keys are unchanged.
  • extract_from_data / extract_from_pandas wrap TOON data in <data> and prepend TOON_DATA_NOTE, a one-line explanation of the format.
  • render_output labels its format hint "Output format:" instead of a fake "SYSTEM INSTRUCTION:" inside the user turn.
  • rag.pipeline.DEFAULT_RAG_PROMPT puts retrieved passages inside <context> tags with the same data-not-instructions note.
  • The web tool descriptions (web_search, web_fetch, read_url, and WebSearchTool.DEFAULT_DESCRIPTION) also say that fetched content is data.

DeepAgent

  • SUMMARIZER_SYSTEM_PROMPT rewritten:
    • 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 step tied to the latest request.
  • format_summarizer_input appends the virtual-filesystem paths and the current todo list, which the agent cannot read back after compaction.
  • _stringify_message labels an earlier summary [earlier summary] so it isn't mistaken for user text.
  • New summarizer_prompt= on DeepAgent, AsyncDeepAgent and both create_* factories, passed through to SummarizationMiddleware / AsyncSummarizationMiddleware as system_prompt.
  • BASE_DEEP_AGENT_PROMPT gains:
    • 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_prompt takes the same argument.
  • TASK_TOOL_DESC_TEMPLATE and SUBAGENT_PROMPT_TEMPLATE ask 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_NOTE is 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 offset paging works.
    • edit_file: unique matching, replace_all for renames, empty new_string to delete.
    • glob: fnmatch * also matches /.
    • grep: Python re syntax, the three output modes and (?i).
  • Parameter descriptions tightened to match.
  • edit_file detects copied read_file line-number prefixes in old_string and says how to fix it. A plain miss suggests re-reading the file.
  • search_tools / load_tools say 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 async clean_json_text_with_ai. Both gain keyword-only json_schema and error.
  • ask_for_json and both Conversation.ask_for_json variants 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_PROMPT in eval.faithfulness asks for a QUOTE: line of deciding evidence before the label.
  • _parse_verdict takes the last label, so a quote containing a label word can't decide the verdict. A SUPPORTED or CONTRADICTED verdict with QUOTE: NONE is downgraded to SILENT. A bare label still parses as before.

Details

v1.14.0

Choose a tag to compare

@github-actions github-actions released this 19 Oct 18:37
cdc518f

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-argon and google_vertexai/gemini-4-argon work in sync, async, streaming, tool and live conversations, with low, medium and high effort 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 APIConnectionError after a minute: requests above 4096 max_tokens now travel over the streaming API and return the same result as before.
  • A ParallelGroup inside an AsyncSequentialGroup or AsyncLoopGroup now 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 companion start finds the lock and leaves the running one alone.
Technical changes
  • Gemini: a shared drivers/_google_genai.py now 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 in Conversation / AsyncConversation and live events, so thought signatures survive tool loops.
  • Gemini usage counts thinking and cached tokens. _effort.google_thinking_level is now model-aware: Argon gets a real medium, older models keep the low/high mapping, and an explicit thinking_budget is never overridden.
  • infra/pricing.py: local rate entries can carry a cost_schedule, evaluated at lookup time so a long-running process crosses a price change; rates/google.json and rates/google_vertexai.json add Argon. Google SDK requirements and the README's Argon section are updated.
  • companion/server.py: instance_lock() takes an OS-level lock (msvcrt on Windows, fcntl elsewhere) on ~/.prompture/companion.lock for 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 the run_async() coroutine when an event loop is already running (which is how every parent async group calls it) and still runs to completion with asyncio.run() from plain sync code.
  • drivers/claude_driver.py / async_claude_driver.py: new _create_message / _acreate_message helpers used by generate_messages (plain and JSON mode) and generate_messages_with_tools. Above STREAM_TRANSPORT_ABOVE_MAX_TOKENS (4096) they call messages.stream() and get_final_message(); below it they keep messages.create(). The stream_transport option forces either path. This also lifts the SDK's refusal of non-streaming requests whose max_tokens could run past ten minutes.
  • dev is brought level with main (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

Choose a tag to compare

@github-actions github-actions released this 14 Oct 03:30
13e3dcd

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 effort option works across providers. Saved keys apply immediately, and local Ollama/LM Studio servers can be probed. claude-code/<model> and codex/<model> run on a subscription login.
Technical changes

Turn records and failures (prompture/games/records.py, trace.py, base.py)

  • Attempt gains id, purpose, rendered prompt/schema, public or partial response, validation, FailureInfo, timing, finish reason, and per-call usage with usage_status/cost_status. Attempts are validated before they are traced.
  • MoveDecision gains record (TurnRecord), assistance and model_selected; existing positional constructors are unchanged.
  • classify_failure maps resilience.errors.classify_error onto FailureKind. Status codes, Retry-After and cause chains are preserved, and text matching is only a last resort.
  • Accounting counts partial, unknown and abandoned calls. source_meta reads the driver's own meta, so extraction's default cost: 0.0 is not taken as an observed price.
  • New failure_policy (rescue/forfeit/stop). Auth/configuration failures, cancellation and budget exhaustion raise TurnFailed/TurnCancelled instead 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/MemoryTraceSink with allowlisted fields, credential redaction, a per-record byte bound, torn-line tolerant read_jsonl_trace, and a warn/raise sink-failure policy.
  • Extraction: ExtractionError.raw_text/stop_reason, stream partial_text, and ask_for_json(on_delta=..., should_stop=...) (StreamStopped closes the stream). Empty replies cut off by max_tokens keep their usage and say why.

Turn control (prompture/games/control.py)

  • deadline covers requests, retries and backoff. With a deadline or CancelToken, requests run on daemon worker threads. A stopped stream closes between chunks. A blocking call is marked abandoned, 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. achoose cancels the turn when its task is cancelled.
  • RetryPolicy gives transient failures a separate allowance with exponential backoff, jitter and capped Retry-After; legacy behavior is kept when it is unset.
  • GameBudget reserves before every request and settles afterwards (calls/tokens/cost/seconds, thread-safe, shareable). An unknown price stops cost-only budgets.
  • TurnEvent sequencing plus a bounded, delta-coalescing EventBuffer. 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's load_state, and illegal histories raise FixtureError.
  • check_hints runs with a no-call driver. evaluate runs in seeded random order with fresh players per sample and raw Sample records.
  • scorecard shows numerators/denominators and a missing list. summary_text, save_run/load_run (versioned), paired_schedule.
  • compare with Preregistration: 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 checkmate evaluator. 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 and allows hints.
  • allows flags 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.
  • Motif gains severity, exact and an evidence witness line; HINTS_VERSION is 3.

Memory and prompts (memory.py, base.py)

  • DecisionMemory (none/previous/window) keyed by GameLine. Decisions stay pending until the line shows the move applied, or commit() is called. Rescues carry no plan. Versioned checkpoints validate game, root and move prefix.
  • ChessHarness replaces its private one-turn memory with memory="previous".
  • context_budget keeps 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_state and sides. 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_robin over game.sides and start states, and a stable schedule_id.
  • run_batch: bounded concurrency, one admission slot per provider per game, shared budget, cancel stops admission.
  • BatchStore JSONL 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 exploratory calibrate with minimum evidence and game/time/budget ceilings.
  • play_match adds terminal_reason, forfeited_by, error, game_id, cancel, and start_game/commit hooks.

Experiments (strategies.py, chess/render.py)

  • Reconsider (exactly one review of a warned choice, keep/stop on review failure) and Proposals (single-call workers plus a selector constrained to the proposed moves, partial-failure rules). Both run through the harness request path.
  • representation="text+image" with RenderedImage and deterministic chess.svg boards (PNG through optional cairosvg). Text fallbacks are recorded with a reason.
  • Scorecards count review fixes and harms, proposal agreement, and image turns versus fallbacks. experiment_status reports live-pending without live evidence.

Providers (drivers/_effort.py, infra/, coding_agent_driver.py)

  • options["effort"] is mapped per API family (Anthropic output_config.effort, OpenAI-style reasoning_effort, OpenRouter reasoning.effort, Gemini thinking_level) across sync and async drivers.
  • Claude streaming supports schema-enforced JSON mode (structured output or a forced extract_json tool) with cache breakpoints budgeted for the tool. OpenAI forwards a per-request timeout.
  • CredentialStore writes 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 with prompture setup) and local_servers (parallel probes of Ollama/LM Studio model lists).
  • claude-code/<model> and codex/<model> drivers (sync/async, plugin-registered) run the signed-in CLI in an empty temp directory. cost is 0 with the CLI's estimate kept in reported_cost.

Docs and examples

  • docs/GAMES.md covers the APIs and the host integration contract.
  • examples/game_progress_example.py, game_evaluation_example.py and game_tournament_example.py use local drivers or engines unless --model is passed.

Details

v1.13.10

Choose a tag to compare

@github-actions github-actions released this 13 Oct 04:14
9e4c0f9

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 sends temperature to those families, and the effort option reaches output_config.effort.
  • ANTHROPIC_API_KEY now works as a fallback when CLAUDE_API_KEY is unset.
  • Per-call api_key. extract_with_model and extract_and_jsonify accept 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 reporting null raised TypeError out of a rotating file handler.

New: prompture.marketing

  • BrandContext holds 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= to Agent, AsyncAgent, Assistant and extract_with_model. It goes into the system prompt with rules against inventing numbers, testimonials or contact details.
  • ClaimsChecker flags 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 through FaithfulnessEvaluator.
  • 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)

  • FeedbackLog records user corrections. They are summarized into rules in a ## Feedback rules section, without touching the rest of the instructions.
  • Old and new instructions are scored with an LLMJudge whose rubric includes every rule.
  • The change is promoted through PromotionLedger only 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 the api_key keyword. Without this, the dev run on 97bcbb9 failed.
  • Locally: ruff check . and bandit -r prompture/ pass, and pytest gives 5,364 passed, 89 skipped.

Details

v1.13.9

Choose a tag to compare

@github-actions github-actions released this 09 Oct 02:00
d51d65a

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_stream
  • ClaudeDriver / 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, as generate_messages and the Responses API branch already did.
  • _prepare_claude_vision_messages passes through a block that is already in Anthropic's shape (its source a plain dict), so preparing twice is harmless. The async Claude _do_generate now 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

Choose a tag to compare

@github-actions github-actions released this 05 Oct 04:06
d8d0c3c

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-stop abandons a stuck or unwanted queue: the running step is cancelled, the run stays in history as stopped, and the next prompture delegate can 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, run prompture delegate instead — with when not to delegate, the exit-code table and the answer/resume/stop loop.
  • prompture skill install now also targets Kimi Code (~/.kimi-code/skills) and the shared ~/.agents/skills convention, 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-stop posts /v1/automations/current/stop through 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 delegate fail with 409 and no way out short of a companion app. The 409 message now names both delegate-status and delegate-stop.
  • README's "Delegating a task" section lists the new command.

Skill targets (prompture/skill/__init__.py)

  • TARGETS gains kimi (~/.kimi-code/skills/prompture) and agents (~/.agents/skills/prompture); marker-file ownership and uninstall refusals are unchanged, so foreign directories are still never touched.
  • prompture/cli/skill_cmd.py exposes both in --target with 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/4 and 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 real CompanionServer with a fake agent runner).
  • tests/test_skill_install.py: install/uninstall round-trips for the kimi and agents targets.

Details

v1.13.7

Choose a tag to compare

@github-actions github-actions released this 05 Oct 03:32
1ff51be

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-answer and delegate-resume follow 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: GameHarness offers candidate moves, puts their names in the reply schema as an enum (enforced natively by drivers with schema support, such as Ollama's format), validates replies through Game.parse_move, retries with the rejection reason, and falls back to the engine's best or a random move with MoveDecision.rescued=True. Usage is summed across attempts.
  • level (0-5) controls which moves are offered (all, tagged, blunders removed by a blunder_drop in winning chances, engine top-N unranked, top-N ranked with scores, best only); concepts filters which motif tags are attached. Engine levels use Game.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.
  • Game and Engine protocols, plus Motif, Score, Candidate, Attempt, MoveDecision and check_concepts, are the extension points for new games.
  • games/match.py: play_match(game, players, state=None, max_moves=400) returns a MatchResult with the winner, moves, per-side rescues and a finished flag; EnginePlayer and RandomPlayer implement the same choose(state) interface as a harness.
  • games/connect4.py: Connect4Board (flat cells, play/undo, winning_cells, immediate_wins), tags for win, block, threat, double-threat, safety and center, a prompt with a grid plus per-column stacks, and Connect4Engine, a centre-first alpha-beta search over open-line windows (default depth 6) with "wins in N" / "loses in N" labels.
  • games/othello.py: OthelloBoard with flips and forced passes (PASS), tags for corner, x-square, edge, mobility and safety (newly conceded corners), and OthelloEngine, alpha-beta on a positional weight table plus mobility (default depth 4).
  • games/chess/motifs.py: describe_move tags facts (capture, check, promotion, castling) and concepts (mate, back-rank-mate, fork, pin including 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), ChessEngine wrapping a UCI engine path or SimpleEngine into Lichess winning-chance scores, and ChessHarness as a GameHarness subclass.
  • games/chess/puzzles.py: load_puzzles, play_puzzle and run_puzzles read Lichess-format packs (opponent's move first, any mate accepted on the last move) into a PuzzleReport with solve rate and rescues.
  • New chess extra (chess>=1.10), included in all; prompture.games.chess raises a clear install hint without it.

Task delegation (prompture/cli/delegate_cmd.py)

  • delegate, delegate-answer, delegate-resume and delegate-status drive the companion's automation queue over its loopback API with the bearer token from ~/.prompture/companion.json, through a stdlib urllib client.
  • The client refuses any companion URL that is not 127.0.0.1 or localhost, so the bearer token never leaves the machine, matching the hook client.
  • A missing companion is started detached (DETACHED_PROCESS on 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 0 finished, 1 failed, 3 waiting for an answer, 4 stopped or detached. --json, --no-wait, --timeout, --agent, --model and --cost-cap are supported.
  • Registered through _COMMAND_MODULES in cli.py; documented in the README.

Tool-context management (prompture/agents/tool_context.py)

  • ToolResultPolicy and shape_tool_results move results over per_call_tokens (default 4000), or the largest results of a round over batch_tokens (default 12000), into an ArtifactStore, leaving a head/tail preview and an artifact:// handle; read_tool_result(handle, query, offset) is registered only once something is stored. Error results are cut, never offloaded; offload=False restores plain truncation.
  • ToolLoader sends search_tools / load_tools instead of the full schemas when they would exceed ~8k tokens (defer_tools="auto"), keeps loaded tools for the rest of the conversation, and always sends preload_tools.
  • repair_tool_pairs gives 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.
  • Conversation and AsyncConversation gain this through ToolContextMixin; Agent and AsyncAgent accept tool_result_policy, defer_tools and preload_tools and pass them through.
  • ArtifactStore gains text(handle, scope=...), a scope-checked text read, and clear(scope=...).
  • create_deep_agent and its async twin default to summarize_at_tokens="auto", which resolves via resolve_summarize_threshold to the smallest of 80k, 75% of the model's context window, and the window minus the output reservation (from max_output_option or the model's limit), with a 2k floor and a fallback to 80k for unknown models.
  • SUMMARIZER_SYSTEM_PROMPT rewritten into fixed sections (task, user messages, findings, actions, errors, open items, current step) that keep exact values and artifact:// handles.

Details

v1.13.6

Choose a tag to compare

@github-actions github-actions released this 03 Oct 09:09
4d7e98a

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_anilist finds 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() in prompture/capabilities/challenge.py, exported from prompture.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
  • JinaReaderBackend and DirectBackend raise RequestRejectedError on 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.py with AniListReader, registered among the built-in readers after Wikipedia. It handles anilist.co anime, manga and character URLs by ID and queries the public GraphQL API at graphql.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 / BackendUnavailableError so they fail over like the other readers
  • search_anilist() is exported from prompture.tools.web to go from a title to its page. AniList's search matches romanized titles without macrons but returns unrelated entries with them, so fold_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

Choose a tag to compare

@github-actions github-actions released this 03 Oct 08:36
ffd694f

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_url understands 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 transcribe and summarize_media turn 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 in
  • prompture 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 exa registers MCP servers once and reuses them by name; secrets are stored only as ${ENV} references
  • prompture doctor shows every provider, tool, media piece, MCP server and binary with its status, active backend and fix; prompture setup / configure store keys in an owner-only file with profiles
  • prompture skill install gives coding agents a skill describing all of this, and prompture check-update / watch report 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 honors max_tokens, temperature and the other sampling controls
Technical changes

Capability foundation (prompture/capabilities/)

  • BackendChain tries Backends in order, skips unavailable ones and classifies failures through classify_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 as exc.attempts. Results are ChainResults with route = {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_error pins UnsafeURLError (fatal), ChallengePageError and ResponseTooLargeError (failover) to fixed actions, so reset_error_rules() can't turn an unsafe URL into a failover
  • probe_command runs a side-effect-free command with a UTF-8 child env and stripped interpreter variables and classifies ok | missing | broken | timeout | error; exit 126/127, exec failures and "No Python at" style output count as broken. cached_probe shares results for 60 s
  • HealthStatus and a capability registry (register_capability, check_capabilities) that doctor walks; a crashing check becomes an error row instead of breaking the report
  • normalize_public_http_url allows only public http(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_PRIVATE is the escape hatch
  • GuardedHTTPAdapter checks 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_get mounts it on its own session and on any requests.Session passed in; connections to a configured proxy are exempt
  • safe_get follows redirects manually and re-validates each hop, streams the body with a byte cap, detects challenge pages and raises typed HTTPStatusErrors that carry status and headers. Proxies come from PROMPTURE_<BACKEND>_PROXY or PROMPTURE_PROXY
  • is_challenge_page scores combined Cloudflare / captcha markers in the first 4 KB, requiring more evidence on 2xx responses
  • scrub_url_credentials / scrub_secrets remove URL userinfo, bare user:pass@host, secret-looking query/fragment parameters, known API key shapes and bearer tokens. They're applied to DriverHTTPError messages, every capability error and health row, and a new SecretScrubbingFilter installed by configure_logging, which also scrubs rendered tracebacks (exc_text) and stack info
  • prompture/tools/named.py resolves "namespace:name" tool specs; Agent, AsyncAgent and DeepAgent accept them next to callables and ToolDefinitions

Web tools (prompture/tools/web/)

  • Search backends tavily, exa, serper, brave, jina, searxng and keyless exa_mcp (a minimal JSON-RPC client over streamable HTTP, no mcp dependency) behind web_search / asearch, with domain filters, recency, URL dedupe and a SearchResponse carrying the route
  • web_fetch chains jina_reader and direct (safe_get + stdlib HTML to Markdown, trafilatura when installed), validates the URL before any backend sees it, pages long content with start= and caches for 10 minutes
  • read_url routes to readers (YouTube transcript API, then yt-dlp subtitles, then transcription; GitHub REST, then gh; HN Firebase; arXiv API; Wikipedia REST; podcast enclosures; feeds), with per-reader step overrides via PROMPTURE_READER_<NAME>_BACKENDS
  • search_platform for YouTube, GitHub (repositories, issues, code), Hacker News and arXiv; WebToolkit and the web: namespace build agent tools that return strings and never raise
  • WebSearchTool keeps its signature; with nothing configured it now falls back to keyless search instead of raising
  • New prompture/tools/web/cache.py: search, fetch, read_url and platform search share a cache built on the existing MemoryCacheBackend / SQLiteCacheBackend (default SQLite at ~/.prompture/cache/web_cache.db, 5,000 entries; PROMPTURE_WEB_CACHE=memory|off, PROMPTURE_WEB_CACHE_PATH). Lifetimes come from ttl_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_url caches 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 carry route["cached"] / cache_age_s plus a "cached N min ago" footer. Every entry point takes use_cache= and cache_ttl=; clear_web_cache(), cache_info() (read-only, never creates the database) and a web_cache doctor row round it out

Media understanding (prompture/media/understand/)

  • transcribe accepts a URL or file, downloads direct audio through safe_get and video/podcast pages through yt-dlp, then downmixes and chunks long media with ffmpeg and stitches the timestamps. It enforces caps on source size, chunk count and upload bytes, and cleans up temp files in finally
  • auto picks the first configured STT provider (Groq, OpenAI, ElevenLabs; reorder with PROMPTURE_STT_PROVIDERS); a second provider is used only with allow_provider_fallback=True
  • New GroqSTTDriver, STT usage recorded through record_media_usage, summarize_media map-reduce summaries with timestamped key points, transcribe_media / summarize_media agent tools, and prompture transcribe

Doctor and updates

  • prompture.doctor.check_all() returns a DoctorReport with a versioned prompture.doctor/1 JSON schema. The providers section checks key presence, base URLs, SDK importability and driver import offline, and --live lists models only, never generating anything paid. Unconfigured providers collapse into one row
  • prompture doctor, check-update (one PyPI call cached for 24 h, with changelog highlights) and watch (exit 1 when something is broken, 2 for an update only with --fail-on-update)
  • The companion info payload gains a health summary, cached and never blocking

Setup and credentials

  • CredentialStore writes ~/.prompture/credentials.yaml only on first write, owner-only (0600, or a user-only ACL via icacls on Windows), atomically, refusing symlinks and junctions, with a 1 MB read cap and profiles
  • Settings reads the store below env vars and .env; get_config_value gives the same precedence to proxies and backend overrides
  • detect_environment tells local machines from servers (container, CI, SSH, headless); prompture setup, configure and reset all support --dry-run, validate keys live and never echo them

MCP hub (prompture/mcp/hub.py)

  • Named servers in ~/.prompture/mcp.json plus a project .prompture/mcp.json that wins by name. Literal secrets are refused in favor of ${ENV} references resolved at connect time
  • resolve_mcp_tools backs mcp:<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 call
  • http_session for streamable HTTP, presets (exa, deepwiki, github, fetch, time, git, memory, filesystem, sequential-thinking), ...
Read more