Changelog
90 min read
All notable changes to shipit-agent will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Unreleased
Nothing yet.
v3.1.0 — 2026-11-09
Added
- Local Chrome Workspace companion with a docked, height-aware chat panel, compact composer, streamed replies, page snapshots, screenshots and bounded image/document uploads. Default model selection includes Gemma 4 31B and 26B-A4B on Bedrock; model changes preserve backend chat history.
- Optional Google Docs, Sheets and Slides API tool with per-chat change
proposals, explicit Apply controls, revision checks for Docs/Slides and
best-effort conflict detection for Sheets. Added the
browserinstall extra and local setup documentation. - Browser bridge validation and session-isolation tests, Workspace proposal tests, attachment tests, streaming decoder tests and an opt-in five-turn real Bedrock HTTP/SSE smoke test.
sandbox, the Claude Agent SDK option: bash andrun_coderun under Seatbelt on macOS or bubblewrap on Linux, writing only in the project, theadd_dirsfolders, temp andfilesystem.allowWrite, with the network off unlessnetwork.allowedDomainsis set.excludedCommands,allowUnsandboxedCommands(dangerouslyDisableSandbox, approved like a risky command),autoAllowBashIfSandboxed,failIfUnavailable,filesystem.denyWrite/denyRead,allowLocalBindingand Unix sockets are enforced. Domains are not filtered one by one. Also usable directly asBashTool(sandbox_policy=...).
Changed
- Every module is under 300 lines except the package
__init__, which is only the public import list. 38 modules split by job into sibling modules and mixins; public names import from the same paths as before.shipit_agent.appsis now a package, andcreate_deep_agentlives indeep/deep_agent/create.py(still importable fromfactory). - The license is declared as an SPDX expression (
license = "MIT"), as setuptools requires from 2027; building now needs setuptools 77 or newer.
Fixed
-
Relative sandbox paths point at the project.
filesystem.denyWrite,denyReadandallowWriteentries such as".env"were resolved against the Python process's working directory, sodenyWrite: [".env"]protected nothing when the process ran from another folder (a server, a notebook). They now resolve against the project root. -
System messages reach Anthropic models.
AnthropicChatLLM.complete()dropped system-role messages unlesssystem_prompt=was also passed, so callers that send their instructions as a system message lost them: the verifier's pre-tool and progress checks (vetoes never fired), memory summaries and the autopilot critic. They now become the system prompt. -
The verifier's stats count checks made inside a run. Tools wrapped by
VerifierNetworkcalled the checker directly, bypassing the counters, soverifier.statsread zero however many calls it vetoed. -
verify_before_stopuses the agent's project. The verify gate looked for the test command in the process's working directory unlesspersist_large_tool_outputswas on, so it was silently off for agents whoseproject_rootwas elsewhere. -
Autopilot counts tokens and dollars.
GoalResultcarried no usage, so Autopilot's token and dollar meters read zero andmax_tokens/max_dollarscould never stop a run.GoalResult.metadatanow has the summed usage, the model and the tool-call count of its steps. -
A caller's system messages in
history=are kept. System context passed in the history, such as aConversationMemorysummary, was dropped with the stored session prompts; it is now added to the turn's system prompt. -
The cost alert log line shows the budget to four decimals, like the spend.
-
Memory search finds facts by their words.
InMemoryMemoryStoreandFileMemoryStorematched only facts containing the exact query text, so thememorytool's natural searches ("deploy region") found nothing. Facts now rank by the query words they contain, the exact phrase first. -
RAG sources are reported again.
result.rag_sourcesand the streamedrag_sourcesevent were always empty in an agent run: the source tracker was thread-local and the agent loop runs on a worker thread. It is now a context variable, which the worker inherits; concurrent runs still keep their own sources. -
RAG citation numbers match the sources.
rag_searchnumbered each search's results from[1], so after a second search the model's[1]andrag_sources[0]were different chunks. Inside a run, every hit is now labelled with its run-wide number, a chunk found again keeps its first number, andrag_fetch_chunkincludes the chunk'scitation. -
Browser connection failures remain visible inside the settings dialog and failed token changes preserve the displayed conversation. Unfinished runs are no longer presented as complete; malformed model selections return a client error without losing the chat.
-
Chat-session streaming forwards images and files and closes its underlying stream when the consumer disconnects.
-
A call that needs approval no one can give now says it was not run and what to do instead: finish the task and name what still needs approval. Told only "requires human approval", Gemma retried
rmand then asked for confirmation in text until it ran out of turns. -
Renames across many files: the bash guidance names one portable
perl -pi -ecall, then agrep_filescheck, instead of a read and an edit per file, and puts throwaway scripts in a temp folder. Gemma's rename across eight modules passed 5 of 5 runs in 7-12 turns (before: 3 of 6, up to 40 turns and 863k input tokens). All 18 benchmark tasks pass on Gemma 4 and Claude Sonnet 5.5; Claude's input tokens fell 11%. -
The benchmark paired tool results with calls by position, so one call without a result shifted every later one in the trace; it pairs by id.
v3.0.0 — 2026-11-07
Feature and default parity with the Claude Agent SDK and Claude Code, under the same API names, on every provider. Several defaults changed: see docs/guides/upgrading.md, and docs/releases/3.0.0.md for the full notes.
Added
shipit_agent.sdk:query(),AgentOptions, typed messages,AgentClient(multi-turn, interrupt, file rewind), saved sessions (resume, fork), in-process MCP tools (@tool,create_sdk_mcp_server),AskUserQuestion.shipit_agent.harness: Claude Code plugins, settings files (opt-in) and shell command hooks.- Permission rules in the SDK's syntax, checked in its order;
allowed_tools,disallowed_tools,max_turns,dontAskmode. - All ten SDK hook events; hooks may defer a call to resume it later.
- User-defined sub-agents; background tasks with notifications and
task_stop;fallback_model. clear_tool_results(off by default) for very long runs.- MCP servers over SSE (
{"type": "sse"}),continue_conversation,add_dirs,env,betasandmax_thinking_tokensonAgentOptions. - A run's usage, and
ResultMessage.usage, carries the SDK's shape (input_tokensuncached, the cache counters,output_tokens) for every provider. scripts/bench: 18 real tasks checked by outcome, with traces,--repeatand run comparison.
Changed
- No step limit by default (
max_iterations,max_budget_usdare opt-in). - Read-only tool calls run in parallel by default.
- Claude Code's working set of tools stays resident; skills load on demand.
- The automatic planner is opt-in.
- Bash: full shell under permissions, two-minute default timeout, long commands continue in the background.
- The fixed per-turn prompt is about 42% smaller.
shipit chatprints a compact transcript by default.
Fixed
- Without the
anthropicpackage, Anthropic requests raised nothing and went to gpt-oss on Bedrock; they now raise with the install command. - The native Anthropic adapter sent shipit's tools in OpenAI's shape, so the API rejected every request that had a tool; they are now converted.
- Streamed text could arrive after
run_completed, and the transcript then showed the answer twice. - Dropped connections are retried instead of ending the run.
- A background sub-agent's whole work shows in the live display.
- Current Claude models, gpt-oss and Gemma 4 on Bedrock are priced at their own rates.
- The docs build (
mkdocs --strict) failed on a link outside the docs folder. - The installed package now includes the packaged skills catalog and agents
(
skills/skills.json,agents/agents.json); the wheel had left them out.
v2.2.1 — 2026-10-07
Security
bash(including background jobs read viabash_job) andrun_codespawned model-written commands with the full host environment, so any secret in the process env (SECRET_KEY, DB passwords, API keys loaded by dotenv) was readable withprintenv/os.environ. Their child processes now get a scrubbed environment:PATH,HOME,USER,LOGNAME,SHELL,LANG,LANGUAGE,LC_*,TERM,TZ,TMPDIR/TMP/TEMP,PYTHONIOENCODINGand the Windows basics.SSH_AUTH_SOCKis not kept.
Changed
- Behaviour change: commands run by
BashToolandCodeExecutionToolno longer see host environment variables outside that list. Opt in with the new keyword arguments on both tools:env_allowlist=["NAME", ...]passes named host variables through,extra_env={...}sets explicit values (e.g.{"HOME": workspace}), andinherit_env=Truerestores the previous full-environment behaviour.run_codewithsandbox=truealso passesDOCKER_*to thedockerCLI.bashstill runs a login shell (bash -lc), so profile files can re-export variables; overrideHOMEviaextra_envif that matters.
Fixed
- A text tool call such as
{"path": …, "content": …}fit several file tools (each declarespath) and was dropped as ambiguous, so the file was never written. When several tools accept a nameless object, the one tool that declares every key it carries is chosen; a genuine tie is still left alone.
v2.2.0 — 2026-10-02
Fixed
- A model that kept calling a tool after the runtime switched to a text-only
step looped to
max_iterations(16 model calls, ~70k tokens, seen live with a small model). Calls on a text-only step are now ignored and the text is the answer; emitstool_calls_ignored. - The "you have not called any tool yet" reminder was sent as its own trailing user message, and small models answered it ("I am ready for your request") instead of the question, or called a document tool for facts already in the chat. It now rides on the request as a labelled note; after a tool result it still follows, marked internal.
- Text-call healing promoted any JSON object to a call of a no-argument tool
(its empty schema skipped the key check). A model's JSON answer became a
phantom
list_documentscall and vanished from the reply, structured-output answers were re-run as tool calls, and genuine nameless calls became ambiguous and were dropped. A nameless object with keys never matches a tool that takes no arguments. on_user_prompthooks were documented but never called; both runtimes now run them before anything else sees the prompt (rewrite, or deny withprompt_blocked).
Added
- Stop hooks:
AgentHooks.on_stopsees the answer the agent is about to give and can send it back to work with a reason (Claude Code-styleStophooks), in sync and async runs. Bounded byMAX_STOP_CONTINUATIONS(3); emitsstop_blocked,stop_unresolvedandstop_hook_error. StepRouter(shipit_agent.routing): a drop-in LLM that picks the model per step of a run. Routine steps (reading successful tool results) and summaries run onfast; planning a turn, recovering from a failed tool, side calls and the final answer run onstrong. A fast step's text is buffered: if it calls tools the narration is released, and if it starts answering the step is re-run onstrong, so the user never reads the small model's answer or sees text twice. Works for sync, async and streaming runs.max_session_tokens: a soft token budget across every run of an agent or one chat session, checked between steps; an exhausted session stops before spending.agent.session_token_usageandagent.reset_session_usage().- Diagnostics report
discarded_tokens,session_token_limitandsession_tokens_used.
Changed
- The user's real prompt is tagged
metadata["turn_start"], so tools can tell it apart from runtime-injected user messages (reminders, retry nudges). - A routed step's discarded call counts toward usage, cost and budgets but not toward context calibration, which is now keyed on the model that ran the step.
Limitations
- Early abort of a fast step needs an adapter that stops streaming when the
text callback returns
False(OpenAI-compatible and LiteLLM do). The native Anthropic adapter ignores that return value, so there the fast call runs to completion before escalating. - Savings depend on the price ratio between the two models and on how often fast steps escalate; they have not yet been measured against live models.
v2.1.0 — 2026-09-29
Added
- Schema-budgeted automatic tool discovery for ordinary
Agent, streaming and async runs. Large catalogs start with a small resident set plus discovery; authorized tool schemas load on demand.DiscoveryPolicycontrols the initial count, schema budget and reuse. Discovery does not grant permission. - Bounded, schema-fingerprinted discovery checkpoints for reuse across turns,
revalidated against the current authorized catalog. Corpus-weighted lexical
search supports authored
discovery_terms, exact names and MCP metadata. - Opt-in
progressive_skills=True: advertise enabled skill metadata and load instructions on demand with fresh per-run state. Explicit/default skills retain their existing behavior; loading a skill grants no new tools. - Optional
max_task_tokenssoft per-run budget. Budget stops retain completed tool evidence and mark work incomplete. Missing/partial input-output usage stops budgeted continuation; the iteration-cap summary also respects the budget. In-flight calls/retries can overshoot; this is not a spending cap. - Usage diagnostics separate estimated request content from reported main and compaction usage, track prefix changes, and distinguish partial accounting and missing cache counters from complete reports. Cache hits are not assumed.
ToolOutput.from_recordsprovides bounded, field-selected model views while retaining canonical evidence for the host.- Reproducible live fixture evaluations, 100–500-tool discovery benchmarks, loopback HTTP/SSE MCP failure-isolation tests, and a custom harness guide.
Fixed
- Preserve native assistant tool calls and matching tool-result IDs when accepting wire-style chat history. Preserve provider finish reasons and expose incomplete output rather than treating every stop as success.
- Honor separate system prompts in OpenAI/LiteLLM calls, including compaction instructions. Improve summary chronology, exclude summaries from user-turn counts, and retain the newest user turn under tight compaction budgets.
- Normalize inclusive versus exclusive cache accounting without changing raw provider counters, including compaction usage and cost calculations.
- Prevent plugin tool/hook reactivation during chat cloning, isolate hook lists, and preserve host-supplied tools when skills contribute capabilities.
- Deduplicate shared tool guidance; bound skill descriptions; validate skill reference containment; avoid constructing unused builtin tool catalogs.
- Propagate nested tool failures through code-mode results and preserve streaming alongside tools. Extend MCP reconnect/session-affinity coverage.
Validation and limitations
- Latest full Shipit regression: 4,514 passed, 31 skipped; one collection warning. Targeted DRK integration after the final budget fixes: 87 passed.
- Real Gemma 31B/Bedrock with controlled MCP data: 40-message session passed 40/40 evidence checks with three compactions; a fresh 20-message stream passed 20/20 with 537 text deltas, six completed tool calls and one compaction.
- Budget-enabled live fixture run: 10/10 scenarios, 42,040 input / 391 output tokens, including 2,368 reported cached input tokens. No universal cache savings, all-provider live coverage or production-connector reliability claim.
- The offline 500-tool benchmark reduced serialized request characters 89.1% while adding one request. This is not a billed-token or intelligence score.
- Existing plugin/hook composition is supported; executing Claude Code/Codex as a backend is not implemented. Progressive cross-turn skill-body deduplication and session-wide budgets remain future work.
See controls and detailed validation, efficiency measurements, discovery review and custom harness composition.
v1.9.9 — 2026-08-19
Live-trace-driven fixes for Gemma 4 on Bedrock Mantle (and every model): fewer tokens, real cost, tools that actually work.
Changed
CORE_TOOLS25 → 13. The deferred-tools resident set held twelve synonyms of the same capability (run_code/execute_code/bash, four "write structure" tools,ask_user/ask_user_async/human_review, …). A model choosing between them guesses right about a third of the time. Trimmed to one tool per capability; everything dropped stays reachable throughtool_search. The deferred first turn drops from ~7.4k to ~3.7k Gemma tokens.execute_codeanddescribe_bindingare kept — code mode depends on them.- Gemma 4 real context window in
get_model_limits: 256K (31B / 26B-A4B), 128K (E2B). Previously a low default made compaction fire at a fraction of the model's capacity ("it forgets things").
Added
- Gemma pricing rows (26b / 31b / e2b) so a run's cost is not silently
reported as
$0.00. An unknown model still resolves toNone, not a zero-rate default — unknown and free are different answers. - "Look before you ask" in
DEFAULT_AGENT_PROMPT: the agent searches for a referenced file/document/error with the tools it has before asking the user to provide it.
Fixed
$ref/$defsinlining on tool schemas (llms/schema_prep.py, wired into the LiteLLM adapter). Pydantic / FastMCP MCP servers emit$defs+$reffor nested argument models; OpenAI tolerates it but a strict OpenAI-compatible validator like Bedrock Mantle often does not — simple tools keep working, nested ones fail quietly, and the model reads as stupid rather than blocked. Each tool's parameter schema is now inlined and dialect-sanitized (cached, never-raising) before it goes on the wire. Host params (max_context_tokens,file_token_limit, …) are stripped off the wire — forwarding one is a 400 on a field never aimed at the model.
v1.9.8 — 2026-08-19
Tool calling engages reliably on OpenAI-compatible endpoints that need it spelled out.
Fixed
- Send
tool_choice: "auto"when tools are offered. The LiteLLM adapter passedtoolsbut nevertool_choice. Most providers assumeauto, but some OpenAI-compatible endpoints — notably AWS Bedrock's Gemma (bedrock-mantle) route, per AWS's own examples — only engage function calling whentool_choiceis sent explicitly; without it the model narrates the call as prose and loops instead of emitting a structured call. A caller can still override ("required"/"none") viacompletion_kwargs. Applies to both the streaming and non-streaming paths.
v1.9.7 — 2026-08-18
Context accounting is real from the first turn, not an estimate.
Added
- Real per-model token counting (
count_tokens,count_message_tokens). Real counts via LiteLLM's tokenizer (already a dependency), memoised, with a safe fall-back to thechars/4estimate — never raises. For models LiteLLM only approximates (some Bedrock ids), theTokenCalibratorstill refines against the provider's actually reportedprompt_tokens, the true ground truth. Agent(fixed_prefix_tokens=…)— forwarded through both runtimes to theCompactor, so a host can pass a real system-prompt + tool-schema prefix count and compaction is accurate from turn one; the calibrator seeds and refines the rest. The accurate-from-turn-one path is: real count → calibrator refines.
v1.9.6 — 2026-08-18
One flaky MCP connector no longer taxes the whole agent.
Added
- MCP resilience — circuit breaker + bounded retry (
ResilientMCPTransport,CircuitBreaker). An MCP connector is a live dependency; when one goes down, gets slow, or starts rate-limiting, it was re-dialled on every step and each attempt waited out the full transport timeout. Now a per-server breaker marks a server open after a few consecutive failures and fails fast (no network wait) until an exponentially-growing cooldown elapses; one success resets it. A rate-limit (HTTP 429 / JSON-RPC-32029) trips it immediately with a long, fixed cooldown — never hammer a server that asked us to slow down. The wrapper is transparent (request/close+ attribute passthrough) and wired intoconnectors.registry, so every hosted (HTTP) and stdio connector is resilient by default. Failures still surface as the readable tool result the model already handles — this just stops the stall.
v1.9.5 — 2026-08-18
Compaction fires at the right time, so long tool-heavy runs stay lean and stop overflowing the model.
Added
- Calibrated, prefix-aware context accounting (
TokenCalibrator). The compaction trigger ran on two systematic under-counts: it ignored the fixed prompt prefix (system prompt + tool schemas, ~16k tokens on a tool-heavy agent) because those live outsidemessages, and it estimated tokens aschars/4, which under-counts dense JSON tool output (~2.5–3 chars/token) — the shape that most needs compacting. Together they fired the trigger late enough to overflow the provider. Now a per-model EMA learns the gap between the estimate and the provider's realprompt_tokens(prompt-cache counters included, so a cache hit can't drag it down), and theCompactorcounts the fixed prefix. The trigger and the retention target derive from one calibrated estimate;estimate_tokensstays a pure function. Clamped asymmetrically (floors at 1.0 — it can only ever compact earlier), model-keyed, warm-up gated. Fed from the exact view sent at each main completion, never the summary pass. Backwards compatible: no prefix + no calibrator reproduces the old messages-only estimate.
v1.9.4 — 2026-08-16
Added
- Text-to-speech: bring-your-own-key.
TextToSpeechTool(provider_keys={...})lets a host pass an org's own OpenAI/Gemini/ElevenLabs key at synthesis time — the backend uses it directly and never reads the shared process env, so a multi-tenant server keepsos.environclean per tenant. The tool's availability gate now also counts a host-supplied key (so a keyed provider is offered even when nothing is in the env), and each backend prefers an explicitapi_keyover its env var. Edge stays free with no key.
v1.9.3 — 2026-08-16
The agent can't declare a coding task done on failing tests.
Added
- Verify-on-stop — evidence-gated coding (opt-in
Agent(verify_before_stop=True)). Wires the shipped verification ledger into the loop so the agent cannot finish a turn that edited code without fresh passing tests. As tools run, aVerifyGaterecords edits (a non-read-only tool that touched a verifiable file — a README/LICENSE edit never counts, and a failed edit never counts) and verify runs (a shell command matching the project's detected test command, by real exit code). When the model tries to finish, an edited-but-unverified turn is sent back with a "run the tests, read the failure, fix it" nudge (bounded, emitsverify_required); once a matching command exits 0, it finishes. If it runs out of steps first,verify_skippedis emitted rather than a silent unverified "done". Off by default; sync loop (which the streaming path runs on) — async parity is a follow-up.
v1.9.2 — 2026-08-16
More deliverables and more voices.
Added
- Text-to-speech: a Gemini backend + a pinnable default provider. The
ttsregistry now includes Gemini (gemini-2.5-flash-preview-tts, wraps the API's raw PCM into a playable WAV) alongside Edge (free) / OpenAI / ElevenLabs, andTextToSpeechTooltakes adefault_provider/default_voice(or readsSHIPIT_TTS_PROVIDER/SHIPIT_TTS_VOICE). A multi-tenant host can now pin an org's chosen voice provider while the model omits it — and with nothing set, selection stays free-first (Edge). An explicit per-callprovider=still wins. present_file— hand a finished file to the user as a deliverable. After the agent generates or saves a file (an image, a spoken clip, a rendered PDF, a CSV), it presents it: the user gets a downloadable, previewable card the way ChatGPT/Codex surface an artifact, instead of a path buried in prose. Plugs into the runtime's existing artifact tracker (it declares the file'spath), previews images inline via the vision bridge, and emits theMEDIA:<path>tag a send pipeline turns into an inline attachment. Sandbox-guarded (an optionalroot_dir); a builtin, so local and hosted agents both get it.
v1.9.1 — 2026-08-16
Closes the ADK-vs-shipit token gap: the loop no longer restates its plan.
Fixed
- Re-announcement damping — the model stops restating the same plan
(
runtime_core.pystep_request,prompts/reminders.py). The last piece of the dithering story: a weak model kept announcing "I will check the evidence and classify the urgency…" across steps instead of acting, each restatement another model call (one benchmark: 3× the tokens for the same answer). When a step's narration is near-identical to one the model already gave this turn, a terse "you already stated this plan — issue the next tool call or answer, don't restate" line is appended to the end-of-context reminder. Reads only the assistant's own narration (no tool payloads), fires only after repetition is observed (a first honest plan is never discouraged), and never on the last step (which already forces the answer). Both loops, via the sharedstep_request. With duplicate-call suppression (1.9.0) and blind-call repair (1.9.0), this closes the three diagnosed causes of the ADK-vs-shipit token gap.
v1.9.0 — 2026-08-16
Media generation, a leaner loop, agent rules, and the groundwork for evidence-gated coding. Nine features, each its own tested change.
Added
-
Media generation — the agent can make images, speak, and film. Three new tools on one pluggable, availability-gated registry pattern (each backend declares a cheap
is_available(); a tool is hidden unless a backend can run):image_generate— OpenAI (gpt-image-1/dall-e-3) + LiteLLM long-tail; returned inline through the vision bridge. Extra:[openai]/[litellm].text_to_speech— Edge (free, no key) / OpenAI / ElevenLabs; returns a saved path + aMEDIA:<path>tag. Extra:[tts].video_generate— Fal / Replicate; blocks (submit→poll→download) and returns an MP4 path +MEDIA:tag. Extra:[video].- Model validation on image + video: a permissive denylist rejects a
clearly-wrong chat/text model (
gpt-4o,claude-*) before the API call, while any plausible generation model (incl. new ones) passes.
-
Advanced file reading (
shipit_agent/tools/file_extract/).read_filenow extracts office/web documents to clean Markdown — DOCX, XLSX (one table per sheet), PPTX (one section per slide), HTML, CSV/TSV, PDF — instead of returning binary soup. Availability-gated per format;[files]extra. -
Agent rules (
shipit_agent/rules/). Durable policy at the agent and tool level —Agent(rules=[...]),.shipit/rules/*.md, and a tool's ownrulesattribute — scoped by path (paths=["tests/**"]) or tool (tools=["bash"]), priority-ordered. The house-style layer that complements skills' capability. -
MCP schema cache + lazy register (
connect(..., cache=True)). A warm start rebuilds a connector's tools from an on-disk cache without spawning the process; the subprocess launches only when a tool is first called, so an unused connector costs nothing. Config-fingerprinted, atomic writes, TTL, and a first-callensure_readyhandshake seam. -
Verification ledger (
shipit_agent/verify/) — the groundwork for evidence-gated coding. A tiny SQLite ledger where an edit marks the workspace dirty and only a matching test/build (exit 0) marks it clean (passed/unverified/not_applicable),detect_verify_commands()project sniffing, and the gate helpers. Ships the standalone library; the verify-on- stop loop guard follows as its own PR.
Fixed
-
The loop stops dithering — duplicate read-only calls are suppressed, not re-executed (
runtime.py,async_runtime.py). A weak model that re-issues a read it already ran this turn now gets a terse "reuse the result" note instead of a re-run (one benchmark: 10 tool calls where 3 sufficed). Scoped to read-only by contract; different arguments always run; both loops. -
Blind-call repair — a rejected call gets the tool's full signature back (
runtime_core.py). Underdeferred_tools, a model that never saw a tool's schema and guessed a wrong argument name now gets the compact callable signature in the rejection, turning a dead bounce into a self-correcting retry.
v1.8.1 — 2026-08-16
Human-in-the-loop, made first-class.
Added
-
@requires_confirmation— first-class tool confirmation (shipit_agent/tools/confirmation.py). A tool declares for itself that it needs a human "yes", with one decorator, wired into the existing permission gate and approval queue:@requires_confirmation("Deletes files permanently.", impact="irreversible") class DeleteTool: ... @requires_confirmation("Large transfer needs sign-off.", when=lambda a: a["amount"] >= 10_000) def wire_transfer(amount, to): ...It goes past a plain yes/no: a
when(arguments)predicate makes it conditional (a cheap call runs free, only the dangerous one pauses); impact levels (default/destructive/irreversible) let a UI colour the approval card; it is a per-tool floor —PermissionEngine.checkasks even under a bypass/allow mode, and only a hard deny outranks it; and because the answer flows through the existing approval machinery, a human can approve-with-edit. A broken predicate fails safe (asks, never skips).
Fixed
- A denied tool is final — the model no longer retries it. After a human declined a tool, the result said only "was NOT run", which the model read as a transient failure and retried — re-prompting the human on every loop. The denied result now states the decision is final ("do NOT retry, rephrase, or pursue the goal another way; silence is not consent"), so the run stops after one denial. Applied to both the sync and async runtimes.
v1.8.0 — 2026-08-15
The theme is a first-class, extensible platform: connections, providers, plugins, and tools each become a clean drop-in directory — one unit per folder — so adding one is a data edit, not a code change.
Added
- Connector catalog — connections as data (
shipit_agent/connectors/). Every integration is an MCP server described by one declarativecatalog/<name>/manifest.yaml; the registry scans, validates, and skips-invalid-with-a-diagnostic (a typo never breaks the catalog). Ships 28 connectors across developer, communication, productivity, data, business, and automation — GitHub, GitLab, Linear, Jira, Confluence, Sentry, Slack, Discord, Intercom, Notion, Asana, ClickUp, Todoist, Google Drive, Postgres, SQLite, Brave Search, Fetch, Google Maps, Airtable, Stripe, PayPal, Square, Filesystem, Playwright, Puppeteer, Memory, Cloudflare.connect(name)turns a manifest into a live MCP server, choosing the hosted (HTTP/SSE) or stdio transport and wiring the per-user bearer token. - Self-refreshing per-user OAuth (
connectors/oauth_manager.py).OAuthCredentialManagerresolves the right user's token at call time and refreshes it transparently near expiry, recovers from a live 401 with one refresh, dedupes concurrent refreshes, and reportsconnected/expired/disconnectedper connection for a dashboard. 38 provider presets carry the authorize/token/refresh endpoints, so a connector's OAuth is oneregister_preset(...).TokenStoreis a small pluggable protocol with in-memory and0600file-backed implementations. - Provider catalog — every model, one profile-per-dir
(
shipit_agent/providers/). Each provider is acatalog/<name>/profile.yaml(display name, auth env, adapter class, default model, capabilities) plus an optionalprovider.pyfor imperative setup. Ten ship: openai, anthropic, bedrock, gemini, vertex, litellm, groq, together, ollama, shipit/echo.build_llm_from_settingsnow resolves through the catalog first and falls back to the inline chain when PyYAML is absent — behaviour is identical, and a new provider is a dropped-in directory rather than a code edit. Profiles carryfallback_modelsfor a coming failover layer. - Plugin system — extend the agent without editing it
(
shipit_agent/plugins/). A plugin is a cleancatalog/<name>/directory: a declarativeplugin.yamlplus aplugin.pyexportingregister(reg), whereregis a smallPluginRegistrarthe plugin uses to contribute tools and lifecycle hooks. Discovery draws from three sources in increasing precedence — bundled, a user directory ($SHIPIT_PLUGINS_DIRor$SHIPIT_HOME/plugins), and pip entry points (shipit_agent.plugins, opt-in viaSHIPIT_PLUGIN_ENTRY_POINTS=1since they run installed-package code) — user plugins override bundled ones, and each source skips-invalid-with-a-diagnostic. Hook points map one-to-one onto the agent'sAgentHookssurface, so a plugin hook is a real wired callback.Agent(plugins=[...])folds each plugin's tools and hooks in at construction. Ships two examples:audit-logandword-count. check_fntool availability gating (shipit_agent/tools/availability.py). A tool declaringrequires_command/requires_env/check_fnis stripped from the advertised set when its dependency is missing — its schema never costs tokens and the model never wastes a turn calling it. On by default; probes are TTL-cached (30s) with a 60s transient-failure grace.
Changed
-
MCP servers connect in parallel at registry build — 20 connectors no longer cost 20× the handshake latency. Registration stays ordered (collision handling unchanged) and a failing server re-raises exactly as before.
-
build_llm_from_settingsis catalog-backed but keeps its exact signature,SUPPORTED_PROVIDERS, thebedrockdefault, everySHIPIT_*_MODELdefault, and all aliases (vertex_ai/vertexai,echo,proxy). Verified against a captured baseline: every provider returns the same adapter class and default model as before. -
s3://image URLs are recognised for Bedrock; base64 stays the default for every other provider, and images are ordered before text for Gemma.
Validated live
- End-to-end agent runs (tools +
deferred_tools+ MCP) against Bedrock (gpt-oss-120b), Vertex AI (Gemini 2.5 Flash & Pro), and LiteLLM→Bedrock — reasoning events streaming, prompt caching active, anddeferred_toolscutting turn-1 prompt tokens by 60–70% with 61 tools held. - computer_use gains app control (
list_apps/focus_app) and a runnable Vertex-Gemini notebook example.
v1.7.1 — 2026-08-12
Added
- Attachments on
stream()—agent.stream(prompt, images=[...], files=[...])now accepts the same image and file attachments asagent.run(), so a streaming caller can attach pictures, PDFs, and code/markdown files and consume events as they arrive.
v1.7.0 — 2026-08-12
The theme is the working set: schemas for the tools a step can actually use, cache hits for everything the step repeats, and recovery that waits out the window instead of burning through it.
Added
Agent(deferred_tools=...)— the tool catalogue on demand (shipit_agent/deferral/). Tool paging, provider-agnostic: core tools keep their full schema in every request; every other tool is listed by name only in the system prompt until it is loaded.tool_searchnow finds and loads deferred tools — matches become directly callable from the next step, and the search result carries a one-line signature per loaded tool so the model can plan the call immediately. Calling a deferred tool directly also works (and loads it).Truedefers everything outside the core set; a list of names defers exactly those. The selection lives onRuntimeCore, so the sync and async loops share one behaviour and the parity test enrols it automatically. Code mode wins when both are enabled.- Backoff between LLM retries —
RetryPolicygainsllm_retry_base_delay/llm_retry_max_delay/llm_retry_jitter. Exponential with up to +25% jitter, in both loops; a 429 retried after half a second is a recovery, retried immediately it is two more 429s. Thellm_retryevent now reports the chosen delay. Set the base delay to 0 to keep the old immediate-retry behaviour. RetryPolicy(request_timeout=...)— per-request LLM timeout, forwarded to adapters whosecomplete()accepts atimeoutkwarg (OpenAI, Anthropic and LiteLLM adapters now do; older custom adapters are detected and skipped, never broken). Without one, a hung connection hangs the run —cancel()only takes effect between steps.
Changed
- Prompt caching now covers the conversation prefix on Anthropic-family
models (native adapter and the LiteLLM path used by Bedrock/Vertex Claude):
a third
cache_controlbreakpoint on the last message means each step's request extends the previous one from cache, instead of re-billing the growing history as uncached input on every iteration. - Failed streaming attempts reset their tool-input parsers before the retry, so a retried completion starts from clean state instead of appending fragments onto a dead parse.
decision_llmnarration tokens are now tracked — narration is a real model call; its usage lands in the run totals andusage_tickevents instead of escaping cost accounting.
Changed (second wave — validated live on Gemma / Bedrock Mantle)
- Async loop parity, for real: the async runtime now actually invokes
compaction and the argument gate (both existed on
RuntimeCorebut were never called on the async path — an async run past the window just sent an oversized request; an empty structured call just ran). The sync loop's nudge-on-stall now uses the sharedRuntimeCoredecision too, retiring a stale pre-fix copy. - Compaction reuses its checkpoint instead of writing a brand-new summary on every step past the threshold, and the summarizer's own tokens are counted in run usage.
- Narration economy:
agent_observationis now always composed from tool metadata — free — so narration spends its one completion per step on theagent_decisionline only. (Previously a configureddecision_llmwas called twice per iteration.) - Two new healed tool-call formats, both observed live from Gemma 4 on
Bedrock Mantle: a declared tool name glued to a pseudo-JSON object
(
tool_search{query: "…"}, unquoted keys, non-ASCII verb prefixes) and Python keyword-call syntax (weather_lookup(city="Paris")). Identity is the model's own literal tool name; junk-key wreckage still fails the bar. - Native
bedrock_mantle/routing:BedrockChatLLMprefers LiteLLM's native Bedrock Mantle provider when a Bedrock API key is present (BEDROCK_MANTLE_API_KEY— the provider authenticates by Bearer token, not SigV4; routing there without a key 401s, verified live). SigV4-only environments keep the OpenAI-compatible shim. Acceptsbedrock_mantle/<id>,bedrock/<id>, or a bare id. - Stream classification:
final_answer,agent_decision,agent_observation,tool_group_*,tool_arguments_rejected,artifact_createdandskills_selectedare now canonical — a reconnecting client no longer silently drops the answer or the record that a call was refused.tool_arguments_rejectedjoined the publicEventType. - Bedrock-Anthropic pricing entries carry
cache_read/cache_writerates (0.1×/1.25× of input), so cached tokens no longer price at $0 there.
Added (third wave — media, MCP robustness)
- Attachments:
agent.run(prompt, images=[...], files=[...])— images by URL/path/base64 (vision models); text/markdown/code files inlined as fenced blocks (every provider); PDFs as native document blocks where the provider reads PDF.Agent(media_parser=...)now actually works (the documented example used to raiseTypeError). read_filecan see — PNG/JPG/GIF/WebP return the image itself to the model instead of UTF-8 replacement soup; PDFs attach as documents.- The vision bridge — any tool result carrying
image_base64metadata (computer_use screenshots, MCP image results,read_file) becomes a user-turn image block on the next step, in both loops. Only the newest 3 images ride each request; older ones become a one-line stub. Message.contentmay be a block list;Message.textextracts prose. Eviction, truncation, compaction and context tracking all handle blocks (images estimate at a flat ~1,500 tokens).- MCP robustness: exposed tool names are sanitized unconditionally
(
deepwiki**ask_question→deepwiki_ask_question; the raw name still goes on the wire); two servers exposing the same name no longer crash the run (the later one is re-exposed as{server}__{name}); both subprocess transports enforce a per-request timeout (60s default) so a wedged server can't hang the run; a crashed-and-respawned server is re-handshaken before its next call; the server'sinitialize.instructionsnow reach the system prompt as one## MCP server:block, and the boilerplate per-tool MCP guidance line is no longer repeated under every tool.
Added (fourth wave — connections, sub-agents, orchestration)
- Connection cards — a connector called with no credential now files a
connection request and returns an actionable result ("Connect Slack — it
needs a token"), so the runtime emits its
connection_requestedcard and the run continues, instead of a dead-end "not connected" string. The/loginparity. All 17 connectors. orchestratorrole — a lead agent that plans, delegates independent pieces to specialist sub-agents (sub_agent+plan_task+ synthesis tools), and combines their reports.Agent.for_role("orchestrator", ...).
Added (seventh wave — tool depth & plan workflow)
- bash tool depth — 600s timeout ceiling (was 120s); an opt-in
unrestricted=Truemode that allows redirects, heredocs, pipes-to-file and command/process substitution (the safe conservative filter stays the default); and a companionbash_jobtool to poll or kill background jobs by id. A rejected command now returns an error result instead of raising (a raised tool exception could crash the run). multi_edit— a batch of exact-string edits applied to ONE file atomically: edits apply in order to an in-memory copy and write once; if any fails, nothing is written. Same read-gate, mtime staleness, and UTF-8 refusal asedit_file.- git worktree isolation —
git_opsgainsworktree_add/worktree_remove/worktree_listso an agent can work on an isolated branch or worktree, in isolation from the user's working tree. - Readability web extraction —
open_urlnow returns structured markdown (headings, lists, paragraphs) with nav/header/footer/aside/script dropped, instead of flat tag-stripped soup that buried the signal. - Compaction re-grounding — after a fresh compaction the most-recently-read files are re-read from disk and injected as current contents, so the model works from the code, not a prose summary of it. Restores the read-gate for those files.
- Plan mode as a workflow — a
present_plantool: in plan mode the agent researches read-only, then submits a structured plan (title + ordered steps) that is captured and surfaced for approval via the interactive-request channel, instead of plan mode being a bare deny-gate.
Added (sixth wave — long-run trust & polish)
- Read-before-edit gate is reset at compaction — fixes a silent
correctness bug: after a file's contents were summarized out of context,
read_files/read_file_mtimesstill claimed it was read, soedit_fileallowed a write against contents the model could no longer see. Compaction now clears the gate, forcing a cheap re-read before the next edit. - Higher default iteration budget —
Agent.max_iterations4 → 12 (16 with skills). A real multi-step task (read several files, edit, test, fix) no longer gets cut to a summary before the context window is the real constraint. - Larger
read_filewindow — 250 → 2000 lines, 12K → 80K chars. Less paging, fewer edits against unseen regions. - End-of-run summary — a
run_summaryevent (andrun_completed.summary) closes every run with iterations, tool calls, compactions, token usage, and an estimated cost. Canonical for reconnecting clients.FunctionTool(read_only=...)also gained a three-state default (None= use the name heuristic).
Added (fifth wave — parallel reads, native structured output)
- Read-parallelization from tool contracts — a tool group where every
call is read-only (grep, glob, file reads, lookups) fans out concurrently;
any group containing a write/send/mutate stays serial and ordered. This
both fixes a latent race (two
edit_files to one path racing across isolated state copies) and delivers fast batched-read execution. Shared decision onRuntimeCore.read_only_calls, so both loops agree.FunctionTool(read_only=True)lets a caller declare a pure function parallel-safe. - Native
response_formatstructured output — whenoutput_schemais set, the tool-less final/synthesis turn now sends the provider's nativeresponse_format(guarded byaccepts_kwarg), guaranteeing parseable JSON where the provider supports it. Never applied mid-loop (JSON-mode would suppress tool calls); the prompt-instruction + validate-with-retry path remains the fallback.
Fixed (sub-agents)
- The sub-agent's system prompt was the delegator's text. Every
general-purpose sub-agent was told how to delegate instead of that its
final message is the whole deliverable. Split into a child-facing
SUB_AGENT_SYSTEM_PROMPT(final-report contract: your final message is all the parent sees; don't ask the human; lead with the answer then the evidence) and the parent-facingSUB_AGENT_PROMPT(now the tool'sprompt_instructions). agent_typere-granted tools the parent didn't have.for_rolere-added the role's full builtin set on top of the inherited subset — a read-only child regainedwrite_file/bash, and 7 roles even regainedask_user_async(a background child could block on a human nobody was watching). The role agent's toolset is now intersected back to the inherited set minus the never-inherited human-interaction tools.- Sub-agents could hang the parent forever.
SubAgentTool(timeout=...)bounds every collect; on expiry the future is cancelled and a failed result is reported. Addedclose()/__del__so the thread pool is released (was leaking non-daemon threads that also wedged interpreter exit).
Fixed
- Eviction no longer corrupts persisted sessions. With
evict_prior_tool_outputs=True(the default), the evicted stubs were being written back to the session store — turn 3 permanently destroyed what turn 1 retrieved, for replay, audit and every future turn. Eviction is now strictly a per-request view: the store always keeps the original transcript. Both loops.
v1.6.3 — 2026-08-10
The theme is spending: what a turn is charged for, and what it gets back.
Added
Agent(reminder=...)and built-in end-of-context reminders — a short instruction placed after the whole conversation, where a model attends most reliably. Three built in, one per phase of a turn, so exactly one applies at a time and none of them is boilerplate: before anything has been retrieved the risk is invention, once something has it is answering from too little of it, and on the last step it is spending that step on a call that cannot run. Appended to a per-call copy of the message list, so it is rebuilt each step, never stacks, and never enters the saved session.Agent(evict_prior_tool_outputs=True)— earlier turns' tool payloads are replaced with a short notice. A message list is cumulative across turns as well as within one, so a search that returned fifteen thousand characters in turn one was billed again on every request of turn two and three, long after the model had written whatever mattered into its answer. The call, its name and its arguments stay — those are what stop the model searching for the same thing twice, and they are a few tokens against the thousands they replace. No message is dropped: a tool result removed while its assistant tool-call message remains is a malformed conversation some providers reject outright.decision_llmwrites the progress narration — opt-in, and only when the primary model produced no prose of its own. A small model spends its whole completion on the tool call (14 to 16 tokens, measured on Gemma), so there is nothing to prefer and a label built from the tool name is all that was honestly available:agent_decisionread "Asking question openai/openai-python." andagent_observationread "Asked question openai/openai-python." Decision prompts carry the request, the previous observation and the pending call — never the conversation, never a tool payload. Observations get a 500-character head of each result, because an observation that cannot see what came back can only restate the tool's name. Roughly 500 tokens for a two-tool turn. Omitdecision_llmand nothing extra is called at all.
Changed
- Tool rules moved into the tool section. The same instruction is followed
far more reliably next to the tools it governs than a few paragraphs earlier
under a general heading. The depth rule was the proof: given fifteen search
hits and a request for detail, the model opened one and answered from it.
Rules now sit under
# Toolsdirectly above the listing, with families as##subheadings, and are emitted only when tools are attached. - Tool descriptions are no longer repeated in the prompt. Every description is already in the JSON schema the provider requires, and that copy is the one the model selects on. Measured on a 43-tool agent, the second copy was 15,286 characters — about 3,800 tokens — re-sent on every step of every turn. What stays is what the schema has no room for: family, read-only status, origin.
- The final step is sent without tool schemas. A tool call emitted there has no iteration left to run in, so the run ends with the model having announced work instead of doing it. Withholding them forces the answer and drops the largest fixed cost in the request from the longest prompt of the run. A single-step agent keeps its tools.
- Both loops share one step-request decision. The async runtime builds its
own request and had received none of the above;
RuntimeCore.step_requestnow returns the messages and schemas for a step and both loops call it. A test asserts neither defines its own.
Fixed
- A call that asks for everything is not run. Healing only inspected calls
written as text; a call arriving through the provider's tool-call field
skipped it entirely, and a weak model produces wreckage there too — observed
live as
search_echo({",'query'": ""}). Keys that clean up to a declared parameter are repaired; a call supplying no usable argument to a tool that declares it needs one is refused, with a sentence naming what to pass. Deliberately not a per-parameterrequiredcheck: a schema's required list does not hold for every valid invocation. - Tool calls are validated against their own schema. A name check asks "could this be a parameter?"; a schema check asks "is this the parameter?" — which is what matters when a filter is optional and a missing one means "return everything". Nameless argument objects are matched to the tool whose schema they satisfy, and list-valued parameters arriving as JSON strings are coerced.
- Invented data is no longer presented as retrieved. Asked for the latest cases, an agent called nothing and returned a table of case IDs, names and dates, describing them as retrieved. Every row was invented and both search tools were attached. An answer that is merely wrong can be argued with; one that claims a retrieval it never performed cannot.
- A greeting is no longer read as a stall. "…just let me know" matched the "let me" intent marker, so the runtime nudged and the model's apology was concatenated into the answer. Fixed structurally rather than by excluding the phrase: a stall is a bare announcement, so a reply ending in a question or saying more than one thing is not one.
FunctionToolno longer advertises**kwargsas a required parameter. It has no default, so it landed inrequired— telling the model to invent a value for**_ignoredand making the declaration useless for validation.- The reasoning channel is searched for tool calls when the model produced thinking but no answer and no calls — the shape that means the call was written into the thinking, where nothing previously looked.
v1.6.2 — 2026-08-10
Added
Agent(observability="auto")— runs report themselves through shipit-watcher with no wiring: traces, the agent graph, scores, datasets and the local ledger. Attaching it used to mean building aWatcherExporter, passing it astrace_storeand remembering to flush, which is the kind of friction that ends with tracing on in one service and forgotten in the rest."auto"is safe as the default because watcher answers "am I configured?" itself — no credentials means nothing is emitted rather than an error, and a missing package falls through. SetTrueto force it,Falseto refuse it; an explicittrace_storestill wins.WatcherExporter— theTraceStorebehind it. Watcher's API is context managers and an agent reports events after they finish, so open scopes are held per trace and closed on the matching end event. A run that dies with a tool still open has its scopes closed anyway.max_tool_output_group_chars(default 48,000) — bounds a whole turn, not just one result. The per-result cap let four tools return 100,000 characters each and pass 64,000 through, every one of them individually "under" the limit.
Changed
- Progress narration prefers the model's own words. A model that calls a tool usually says why in the same response, and that sentence is better than any label composed for it, was already paid for, and improves as the model does. Composition is now the fallback for when it says nothing.
- Observations report what the tool said about itself — from
metadata["summary"], and nothing when the tool said nothing. An earlier draft read the payload instead, pattern-matching keys liketotalout of JSON; only the tool knows whether its total counts results, pages or bytes, so that was the runtime inventing a fact and stating it with confidence.
Fixed
- The narrator no longer conjugates namespaces. It made a verb of
whatever came first in a tool name — right for
get_echo, wrong for every<namespace>_<thing>an MCP server exposes.rl_searchnarrated as "Rling search" andslack_post_messageas "Slacking post message". Leading words are conjugated only when they are verbs; anything else reads as "Running rl search", which is honest about not knowing the tool. - A repeated tool call is answered once. Same tool, same arguments, byte-identical output — the model is told it already has that result and to change the arguments, instead of being handed the same 138,000 characters a fourth time. The tool still runs and its output is still compared, so a changed answer is returned in full.
- A truncated result says what it dropped. The extract now reports how many characters went and how to ask for them, rather than only that it was "shortened".
- The default prompt now says independent tool calls belong in one response. Every tool group in a reported run held exactly one call — not a runtime limit, the runtime takes a list; the prompt had never mentioned it. Turns are what cost, not calls.
v1.6.0 — 2026-08-10
Changed
- Progress narration no longer calls a model.
progress_summaries=Truespent one extra LLM call per iteration, and with nodecision_llmconfigured it spent them on the run's own model — so watching a run roughly doubled its model calls and its bill, on the expensive model, to produce a paraphrase of data the runtime was already holding. The summaries are now composed from the tool calls and their results:Reading guests.csv.before,Read guests.csv.after, and a failure named as a failure rather than folded into the sentence. Same events, same payloads, no second model, no added latency. decision_llmis still accepted so existing callers keep working, and is no longer used for anything. The dead prompt-building helpers it needed (_call_progress_model,_recent_public_context,_bounded_text) are gone.
Fixed
shipit chatwas two model generations behind the rest of the CLI. It kept its own provider→model table, so--provider anthropicgaveclaude-3-5-sonnet-latestin the REPL andclaude-sonnet-5everywhere else — same CLI, same flag, different model, with nothing on screen to say which you had. OpenAI was still ongpt-4o-mini. The curated catalog is now the single source of truth wherever it has an opinion, and a test pins the two together. Providers the catalog does not cover keep their own defaults rather than being blanked.shipit doctorprints a report instead of a dataclass. It printedDoctorReport(checks=[DoctorCheck(name=…— one 2,000-character line you had to read like a stack trace, for a command whose entire purpose is to be scanned. Failures and warnings now come first and carry their details; passing checks are one line.--jsonfor the full structure, and a failing check is a non-zero exit in both — soshipit doctor && deploystops at a broken agent, and a script piping--jsontojqcannot see success where the terminal showed a failure.- A wrong API key says so. Any uncaught error from a command printed a
traceback, so the usual first-run failure — bad or missing credentials —
arrived as thirty frames of the provider SDK with a 401 at the bottom.
The message is now summarised, credential failures name
shipit doctor --provider <name>as the way to find the variable, andSHIPIT_DEBUG=1still prints the traceback in full. Ctrl-C exits 130 rather than reporting itself as a crash.
v1.5.1 — 2026-08-08
Added
- Guides for three features that shipped without them. Triggers, delegation, and agents as tools each had public API and no page explaining when to reach for it.
SqliteTriggerQueue,InMemoryTriggerQueue,fire_all,DelegationPolicyandDelegationAdviceare exported from the package root. Found by checking that every import in the new guides resolves:TriggerRegistrywas exported without the queue you configure it with, so the durable setup — the default anybody running this in production wants — could not be written from the top-level import at all. A test now pins the surface the documentation uses.
v1.5.0 — 2026-08-08
Added
AgentTool— an agent as a tool. Wrap any agent and hand it to another as one focused callable, so a researcher, a reviewer and a writer become tools a coordinating agent picks between by description. Unlikesub_agent, which builds a child from the parent, this delegates to an agent you configured yourself — its own model, its own tools, its own session if you give it one. Nested events surface through the parent's stream, so a delegated run is visible rather than a silence.
Fixed
- A failing child no longer ends the parent's turn.
AgentToollet a child's exception propagate, so one provider being down took the whole run with it. Every other tool reports failure as a result the model can act on; this one does now too, and the parent can say so, try something else, or answer without it. - Delegation cannot recurse forever. An agent holding a tool that
wraps an agent holding the same tool would recurse until something ran
out.
sub_agentalready capped this;AgentToolis the same hazard by another name, so it shares the counter rather than keeping its own — and the refusal says to do the task directly, because a cap that does not name the alternative just gets retried. - A child that returns nothing is no longer reported as success.
v1.4.0 — 2026-08-08
Scheduled jobs stop sharing one agent, skills come from the project, and tool output streams while it is produced.
Added
- Per-job agent configuration. Every scheduled job carried the same
process-wide agent, so "summarise the inbox hourly on a cheap model" and
"audit the repo nightly on a good one" could not coexist. A job now
persists its own provider, model, role, project root, runtime mode,
permission mode, guardrails, MCPs, connections, skills and iteration cap
—
ScheduledAgentConfig, resolved per job byScheduledAgentFactory. The SQLite store migrates itself: existing databases gain the new columns on open, and a job written by an older version still runs. - Project skills.
discover_project_skillsandload_markdown_skillread skills from the repository the agent is working in, so a team's conventions live beside the code they describe rather than in a configuration screen. - Streaming tool output.
ToolOutputChunkandToolRunOutputlet a long-running tool report as it goes instead of arriving whole at the end. - Concurrent web search.
web_searchacceptsqueriesas well asqueryand runs them in parallel. Either, never both — an empty call answers with which of the two to pass.
Fixed
- A permanently broken scheduled job now pauses. Failures were counted
and nothing acted on the count, so a job whose provider was gone ran
every interval for as long as the daemon lived, burning quota and
filling the log with one repeated line. Paused after five consecutive
failures — paused rather than deleted, because the configuration is
intact and
jobs resumeis one command. Resuming clears the count, so a job somebody fixed is not paused again by the history that paused it.max_consecutive_failures=0switches the behaviour off.
v1.3.3 — 2026-08-07
Added
- Breadth as a structural signal. "Go through every document attached"
names no number and no filename, so counting lists, targets and quantities
missed it — and 1.3.2's corroboration floor would then have declined it.
A distributive determiner over a noun ("every document", "each of our
repos", "all the open PRs") now counts. It is grammar rather than
vocabulary, so it holds for wording nobody anticipated, and it reports no
count: breadth says "more than one", not how many, and inventing a number
would feed a fake quantity to the
min_itemsfilter. An adverbial "all" — "is it all good", "tell me all about it" — is not breadth.
v1.3.2 — 2026-08-07
Fixed
- A model may no longer invent a decomposition. Asked "Can you look for
the latest AI news?", Gemma 4 answered
decompose: true, items: 3, "news can be split by topic or source"— three sub-agents for one web search, and not one of those topics appears in the request. The assessor was asking "could this be split?", and almost anything could. It now counts only the pieces the task itself names, is told plainly that a single search or a single read is one call, and a model's yes must be corroborated by something actually in the text. The structural floor already raised a model that undercounted; it now also declines one that overcounted. The count it saw is still reported — overruled, not erased.
v1.3.1 — 2026-08-07
Fixed
delegation=no longer attachessub_agenton every turn. Asked "look for the latest AI news", an agent with automatic delegation on spawned three research sub-agents instead of running one web search. The tool was attached unconditionally on the reasoning that a model deciding mid-run to delegate must find it there; measured against a smaller model, that reasoning fails — a tool on the table gets used, and given a choice between a search and a research crew it takes the crew. The policy already judges each task for the directive, and the toolbox now follows the same judgement. An explicitly passedSubAgentToolis untouched.sub_agentrefuses a task with no substance in it. Observed:sub_agent(task=","), which the tool accepted, spending a model call to return an error. A child sees nothing of the parent's conversation, so the task is its entire brief. The refusal names the alternative, because one that does not just gets retried.- Background delegation says there is no "later". The tool and the
directive now state that anything started with
background=truemust be collected in the same turn — a run ends with its answer, and an uncollected task is discarded. Without this, models announced background work and promised to report back on results that were never coming. - A dashboard blueprint f-string that only parsed on Python 3.12+.
v1.3.0 — 2026-08-06
An agent you have to ask is a tool. An agent that reacts is a colleague. This release is the second half: triggers — something happens, and the agent runs.
Added
shipit_agent.triggers— a trigger registry, a durable queue, and a worker loop.registry.on("gmail")decorates a function that turns an event into a prompt. A trigger builds a prompt, not an agent: the run stays on the agent you already configured, with your credentials, permissions and budget, rather than a second agent nobody is watching.registry.fire(source, data)records and returns. It runs nothing. A webhook must answer in milliseconds and an agent takes seconds; a sender that times out delivers the same email again.registry.drain(agent)runs what is queued and reports each run.registry.run_forever(agent, stop=…)is the worker, stoppable.
SqliteTriggerQueue— the default, and durable on purpose. An event that arrives while nothing is running must still be there afterwards, or "runs on every email" is a claim that fails quietly at 3am. Claiming takes the row, so two workers can drain one queue; an abandoned claim expires rather than stranding the event.- Poison-event handling — an event that fails
max_attemptstimes stops, instead of hiding everything queued behind it. fire_all()for batches, andregistry.summary()for "what is wired, what is waiting" — the data behind a Live badge.
Notes
A handler returning None is how a trigger filters: only RSVPs, only
failures. That is a skip, not an error, and it still consumes the event.
InMemoryTriggerQueue is named the way it is to be hard to reach for by
accident — it is for tests.
v1.2.0 — 2026-08-06
One question, asked of every surface: can you see what the agent did? A run now reports itself well enough to draw a product from — and, where the agent used to answer and forget, it can leave something behind.
Added
Watching a run
- The tree view — the shape of a run instead of its prose: every call
named with its status, the opening intent labelled "Understanding request",
each later paragraph a "Decision", the last one the "Final answer". On a
terminal it redraws in place while the run proceeds, keeping the trunk open
(
├─ working…) until it ends, then erases the draft and writes one clean tree.agent.run_live(style="tree"),render_tree(events, detail=True). - The live panel — an HTML chat card that redraws in a Jupyter cell as the
agent works: tokens land with a caret, tool rows appear in flight and
settle, real output folds away behind each call, cards interrupt the flow,
and the footer counts tokens.
watch(agent, prompt),shape="tree"for the tree,render_chat_html(events)for a finished run. Every selector is scoped so the styles cannot leak into the page around it, and redraws are throttled — a full re-render per token is O(n²) DOM churn that stutters exactly when the answer gets long. - The UI timeline — the runtime's events translated into what a frontend
draws:
reasoning_summary,tool_group_started,tool_call_started,tool_call_completed,agent_decision,artifact_created,final_response. Plain JSON, and causal — a group's settled title arrives in itscompletedstep, so a client never has to undraw a row.stream_timeline(agent, prompt);render_markdown(events)prints the same run as a report. - Progress narration —
Agent(progress_summaries=True, decision_llm=…). A second, cheap model says what the agent is doing while it does it. It never reads the system prompt orreasoning_contentand is called withtools=[], so what it reports is what an observer could have watched happen; a failure emitsprogress_summary_failedand the run continues. Off by default: it adds a real LLM call per step. - Tool groups — one per iteration, carried on
tool_called/tool_completedasgroup_id, so a UI can draw one expandable box per turn (2 tools · 13.0ms) however much narration lands between the calls. final_answer— the answer as its own event, just beforerun_completed, so a client does not have to infer which event carries it.
Leaving something behind
- Apps —
list_blueprints,create_app,set_app_binding,use_app, shipped as builtins under<project>/.shipit/apps. The agent writes a program into the workspace, wires resources into it, and runs it — today, and next week, with no model in the loop. An app is a directory withapp.pyexportingrun(input, env); it runs in a subprocess with no credentials, and itsenvcrosses the same capability bridge code mode uses, so every resource call is gated exactly as the equivalent tool call would be. An app sees only the bindings its manifest names. - Six blueprints —
report,csv_summary,page, and three that produce something worth looking at:dashboard(headline cards and a bar chart),sheet(column letters, row numbers, flagged cells) andworkflow(a pipeline as boxes and connectors). All self-contained — no CDN, no fonts, no script tags — because an artifact that needs the network is not one you can send anyone. - Artifact cards — a file a run produced is a card, not a path in a log:
Q2 Kickoff Brief · Doc · Click to open, in the panel, the tree and the timeline. Any tool that declares a path in its result metadata gets one.
Reaching further
- Automatic delegation —
Agent(delegation=True). Thesub_agenttool is guaranteed to exist, built from the agent's own LLM and its read-only tools; the task is sized by a model (ModelAssessor, one cheap cached call) with a structural count as the fallback and the floor; and the directive is appended to the task, not the system prompt — measured against Gemma 4, that difference is 0 delegations versus 6. It never delegates behind the model's back. - Connection requests — the registry knew what was connected; nothing
turned "I need Slack" into something a user could answer. The agent's
request now emits
connection_requestedfrom both loops, the panel draws a card with the reason, andregistry.resolve(id, accepted=…, credential=…)closes the loop — on accept the credential is stored, so the next state check reads CONNECTED rather than asking again.
Fixed
- The async runtime's
tool_completedwas missingtoolandcall_id, so its transcript could only guess which outcome belonged to which call. read_fileproduced artifact cards for files it merely read.- An artifact card split the tool group that made it.
run_live()raises on an unknownstyleinstead of silently rendering the default view.write_transcript()acceptedtitleandmodelbut droppedprompt.use_appwrote a file and never declared it, so the run produced a page and the transcript showed a JSON blob.- Apps ran in their own install directory, so an app given
path="guests.csv"found nothing.AppStore.run()runs them where the agent works. deny=["*"]denying allow-listed tools is now documented where you meet it, with the correct pattern (allow=[…], default_decision=DENY) beside it.
Notes
progress_summariesanddelegationare both off by default. Each adds real LLM calls, and a runtime that doubles your bill on upgrade is not one you can trust.- Two live notebooks ship with their outputs committed, run against
bedrock-mantle/google.gemma-4-26b-a4b:74_live_streaming_gemma.ipynb,75_live_ui_and_subagents.ipynband76_apps_and_analysis.ipynb. - Still open, and named rather than hidden: the async runtime has no progress
narration and no tool groups, and
Agent.decision_llmsits second in the field order, soAgent(llm, "prompt")positionally assigns the prompt to it. Both are tracked indocs/design/cloudflare-os-gap.md.
v1.1.0 — 2026-08-06
A single release focused on one question: what does an agent look like while
it works, and what should it be allowed to do while you are not watching?
Studied against Cloudflare OS (open source) and adapted rather than copied —
the design notes, including what was deliberately not ported and why, are in
docs/design/modern-agent-upgrade.md.
Added
- The Narrator — a transcript instead of a log. Every tool call renders as
a human verb and target (
Read app.py,Ran code const risk = scoreAcc…), consecutive calls with no prose between them collapse into one row, and the run closes with tokens and cost. Present tense in flight, past tense once it lands; in-place on a TTY, byte-stable when piped.agent.run_live(style= "modern"),shipit code --style,/stylein the REPL. The 50-tool verb table is a set of defaults —register_verb()overrides any of it, and unknown MCP tools narrate through real English morphology rather than crashing on an exhaustive match. - Tool contracts — every one of the 51 built-ins now declares what it is
(
read_only,action_kind,implements_revert,await_decision,auto_approvable,destructive) instead of being guessed from its name. Before this, exactly one tool declaredread_only. - Deferred approvals — a side-effecting call the policy marks
askis queued rather than blocked on, so the agent finishes and you review the batch. Auto-approval rules key on a stableaction_kindtag, applied in order, never past a manual gate, and requiring both the contract's verdict and your enabled rule.shipit code --defer-approvals. - Lockdown — once a tool reports it returned sensitive data, the run may only make observations; every action is denied for the rest of it. Closes a real hole: nothing previously stopped an agent reading your customer list and posting it to Slack in the same turn.
- Code mode —
Agent(code_mode=True)collapses connectors into anenvof bindings reachable from oneexecute_codecall, withdescribe_bindingfor on-demand discovery. Measured on the real catalogue: 25,932 → 11,174 tokens per model call, 57% smaller.envreaches the parent over a capability bridge — the code runs in a subprocess holding a socket, not credentials, and every binding call is gated exactly as the direct tool call would be. - Connections —
connectionslists what is connected, what needs authenticating and what is missing, with per-auth-kind guidance, and lets the agent request one with a reason instead of failing mid-task. - Streaming, three ways —
agent.stream()(raw events),agent.narrate()(settled transcript rows, so a custom UI need not reimplement the collapsing), andagent.stream_sse()(wire-ready). Frames are labelled canonical or provisional and carry a per-processstream_generation, so a browser that reconnects knows what to keep and what to discard. NewPOST /v1/streamon the server;/healthreports the generation. - Checkpoint compaction — per-model token budgets, cuts at a turn (or step) boundary rather than a fixed message count, a six-heading handoff summary, and an explicit instruction not to follow instructions inside the transcript being summarized. Canonical history is preserved; only the replay window moves.
give_up— a real tool with a required reason, surfaced asresult.metadata["gave_up"], replacing inference from prose.- Streaming tool arguments — a file or command appears as the model writes it. Supported on all 13 shipped adapters (Anthropic, OpenAI, Bedrock, Gemini, Vertex, Groq, Together, Ollama, LiteLLM and its proxy); adapters without it degrade to arguments arriving whole.
- Revert —
queue.revert(id)for filesystem writes, snapshot-based. - Shareable transcripts —
--share run.htmlwrites one self-contained file: no network requests, no build step, light and dark.
Changed
sub_agentis now an actual sub-agent. It previously calledllm.complete()once withmessages=[]andtools=[]— no loop, no tools. It now runs a realAgentwith an inherited toolset, supportsagent_typefrom the built-in role registry, runs in parallel viabackground/collect, and streams its work into the parent's transcript. A sub-agent can never do more than its parent: permissions, approvals and guardrails are inherited verbatim, and delegation is depth-capped.- One runtime core.
runtime.pyandasync_runtime.pyhad drifted to the point that ten capabilities existed only in the sync loop. The shared decisions now live inruntime_core.pyand both inherit them; a parity suite asserts neither may override or reimplement one. - A missing tool argument is now a result the model can act on rather than a
KeyError— it names the argument and lists what the tool requires.
Fixed
tool_deniedcarried neithertoolnorcall_id, in both runtimes, so a blocked call was invisible to any renderer.sub_agent'scontextparameter was silently dropped:ToolRunnerstripscontextandselfas reserved names, so supporting context never arrived. Renamed todetails.EventTypehad drifted —tool_deniedandtext_deltawere emitted but undeclared.get_model_limitssplit on.to strip vendor prefixes, turninggemini-2.5-prointo5-pro.- An explicit
context_window_tokenshad an output reservation subtracted from it, so a value of 100 produced a budget of 1. - 18 contracts promised
implements_revertwith zero implementations behind it. Now 7 promise it, all backed by a reverter, enforced by a test.
v1.0.18 — 2026-07-31
Added
- The
shipitCLI, rebuilt as a modern package —shipit code(coding agent rooted in your repo: playbook prompt, --plan/--yes modes, y/n/always prompts),shipit serve(AgentServer: the agent as an OpenAI-compatible API with SSE streaming + Bearer auth),shipit run --role/--guardrails, and catalogs (roles | models | mcp | tools) with a curated latest-model list (claude-opus-5/sonnet-5, gpt-5.6/5.5, Gemma 4, gpt-oss). Stdlib UI kit: named palette, NO_COLOR/FORCE_COLOR, encoding-safe banners. - Guardrails engine — four gates (prompt-injection input blocking, secret/PII output redaction, tool-argument deny rules, indirect-injection sanitization of tool outputs) + max_tool_calls ceiling, strict()/ standard() presets, optional fail-open LLM judge; guardrail_triggered events; Agent(guardrails=...).
- Self-healing tool calls — text-emitted calls (<tool_call> tags, fenced JSON, bare call-shaped JSON) promoted to structured executions: declared-tools-only, span-exact removal, response-side only; plus nudge-on-stall (one capped re-prompt on intent-without-action).
- New builtin tools —
git_ops(structured git, fixed argv, push/reset gated off),notebook_edit(structural .ipynb editing),deep_research(multi-angle sweep → deduped sources → citation-ready digest). - RAG chunk-overlap budgeting — carried overlap is capped by the room remaining under the chunk target so near-full chunks never overflow the embedder window.
- Notebooks 72 (guardrails + deep research, live Bedrock) and 73 (CLI +
power tools), docs guide
guides/shipit-cli.md, README CLI section. - Human-in-the-loop everywhere —
console_permission_prompt(): one reusable [y]/[n]/[a]lways approval callback for any agent; powersshipit chatandshipit code; shareable always-allowed set, injectable I/O, EOF→deny. - Bottom-pinned chat TUI —
BottomInputTerminal(VT100 scroll regions, stdlib): chat scrolls, input never moves; auto-on for real TTYs inshipit chat, transparent plain fallback elsewhere. shipit browse— computer use from the CLI: vision loop streamed as live cards,--showvisible window, consent persistence.--mcpflag + official Playwright MCP — attach catalog servers torun/code/serve/doctorfrom the command line; the newplaywrightentry gives any tool-calling model accessibility-tree browser control.- RAG + Agent example (24) — RRF-fused hybrid retrieval with [document-id] citations, fully offline.
v1.0.17 — 2026-07-17
Observability + live browsing. Langfuse support for BOTH server generations, a downloadable-files tool, a fully observable and watchable computer-use loop, and a security hardening pass. 2006 tests passing (+37 new). 0 regressions.
Added
LangfuseExporter(shipit_agent.tracing_exporters) — ship whole agent runs to Langfuse as a root trace + one child span per tool call (real durations, inputs/outputs, error status). Speaks both server generations with zero SDK dependency: v3 via native OTLP (/api/public/otel/v1/traces), v2 via the classic batch API (/api/public/ingestion);api_version="auto"probes/api/public/healthand picks the wire format. Works with every adapter — including Gemma-4-on-mantle calls that bypass litellm callbacks. Transport failures never break the run. (For LLM-call analytics via litellm: uselitellm.callbacks=["langfuse_otel"]against v3 servers — the classic"langfuse"callback is v2-SDK-shaped and 500s on v3.)download_filebuiltin — binary-safe URL downloads (zip/csv/image/pdf): 64KB streaming with a hard size cap (partials removed on abort), reuses open_url's SSRF/scheme guard, Content-Disposition filenames, no silent overwrites, absolute path returned in metadata.ComputerUseAgent.stream()— the screenshot→reason→act loop is now observable: standard events (tool_called/tool_completed/tool_failedasbrowser.<action>withcall_id+duration_ms) render as live tool cards viaStreamRenderer;run()unchanged.- Watchable, reliable live browsing —
slow_mo=(see the mouse move),settle_ms=500(screenshots taken AFTER the page reacts, not mid-animation),device_scale_factor=1(exact coordinate mapping on Retina), typed keystrokes withdelay=40;storage_state=/save_storage_state()persist accepted consent across runs. - Obstacle-autonomous computer use — system prompt now instructs the model to dismiss cookie/consent walls ("Accept all"), close popups, skip sign-ins, route around CAPTCHAs, and verify field focus before typing.
Fixed
- Quoted action args — models emitting
ACTION: navigate "https://…"produced literal quote characters that Playwright rejects ("Cannot navigate to invalid URL"); quotes (and aurl=prefix) are now stripped in both the text and Anthropic tool_use parsers. - Security —
research_briefnow enforces http/https before fetching (URLs come from model-influenced search results;file:///blocked);AdaptiveAgent.create_toolnow honorscan_create_tools=False(previously ignored) and documents its trusted-developer-code-only contract. GitHub CodeQL/Dependabot: 0 open; pip-audit clean on all shipit-relevant packages.
v1.0.16 — 2026-07-10
The live experience — streaming, cancellation, and professional-grade ergonomics. Real token streaming everywhere, one-call live runs with rich tool cards, safe stops, stale-proof edits, model-written compaction, and a sharper CLI. Works with any LLM provider. 1969 tests passing (+13 new). 0 regressions.
Token streaming, everywhere
OpenAIChatLLMstreams for real (was a silent TODO): tokens hit the callback as generated, tool-call fragments stitched by index, usage captured from the final chunk; gateways that ignorestream=Truedegrade gracefully. Lights up Gemma 4 on Bedrock mantle, Groq, and every OpenAI-compatible endpoint.AnthropicChatLLMstreams via the SDK'smessages.streamhelper — all existing parsing (thinking blocks, tool use, server tools, citations) unchanged.
The live experience
Agent.run_live(prompt)— tokens print as generated, tool calls render as cards with args/status/duration, a✔ donefooter closes the run; returns the final answer text.StreamRenderer— the underlying renderer for custom loops;style="rich"(automatic on TTYs) draws rich ⏺/⎿ cards with ANSI colors; prints the answer at the end for non-streaming adapters.agent.cancel()— thread-safe ESC: stops at the next checkpoint, emitsrun_cancelled, returns normally withmetadata["cancelled"]; skipped batch tools get synthetic results so message pairing stays valid.
Reliability
- Edit hardening —
edit_fileblocks when the file changed on disk after the lastread_file(external modification → re-read hint) and returns a compact unified diff with every patch (metadata["diff"]). - LLM-powered compaction — near the context window, old turns are
summarized by the model (decisions, facts, paths, open threads; ~300
words) with a mechanical fallback; the
context_compactedevent now fires reliably.
CLI
- Live
StreamRendererturns (real tokens + cards; spinner retired), inline [y]es / [n]o / [a]lways prompts forask-gated tools (session-persistent always-allows), and--continueto resume the most recent session (~/.shipit/sessions). Fixed a--session-dircrash.
Examples & notebooks
examples/23_bedrock_model_switching.py— Gemma 4 26B ↔ gpt-oss-120B, one function, live-verified.notebooks/71_full_test_drive.ipynb— 13 in-depth sections exercising every capability, executed end-to-end (live Bedrock cells + offline).
v1.0.15 — 2026-07-10
The Super Agent — every sector, clean logs, real deliverables. One release that makes a shipit agent useful to a finance analyst, a marketer, an engineer, a designer, a researcher, and a sales rep alike — and makes every run readable. All of it works with any LLM provider.
Sector specialists — Agent.for_role
- One line to a specialist —
Agent.for_role("finance-analyst", llm=llm)turns any of the 40+ prebuilt role definitions (finance, marketing, engineering, design, research, sales, support, HR, …) into a runnable agent: the role's prompt, its matching builtin tools, and its iteration budget. - Did-you-mean errors — unknown ids raise a
ValueErrorlisting the closest matching roles. - Deliverable-ready roles — 14 specialists (finance-analyst,
marketing-writer, researcher, data-analyst, sales roles, …) now carry the new
build_documenttool.
Prebuilt MCP catalog — connect_mcp
- 12 well-known servers by name —
connect_mcp("github"),connect_mcp("filesystem", args=["/repo"]),connect_mcp("postgres", args=[url]), plus slack, sqlite, puppeteer, brave-search, fetch, memory, sentry, gitlab, and google-maps — each on a persistent stdio transport. - Fail-fast validation — required env vars and the launcher binary
(
npx/uvx) are checked before anything starts; misconfiguration is one clear message. - Resilient MCP calls — a failing MCP tool call (server down, timeout) now
returns a readable tool result the model can react to instead of crashing
the run.
MCPStdioTransport/PersistentMCPSessionaliases are exported.
Polished documents — build_document
- Five formats — PDF reports, Excel workbooks, Word documents, PowerPoint
decks, and styled HTML from one structured payload (
title+sections, orsheetsfor Excel). - Finished, not generated — accent-colored headings, zebra-striped tables,
bold frozen header rows, auto-sized columns; Excel cells starting with
=become live formulas. - Optional dependencies — renderers use
reportlab/openpyxl/python-docx/python-pptxand reply with the exactpip installfix when one is missing; HTML needs nothing.
Clean tool-call logs — format_activity
- rich tool cards —
format_activity(result)renders each call as⚙ name(args) ✓ 228mswith a compact output preview and a run summary footer;format_event_line(event)does the same live for streams. - Timing built in — every
AgentEventnow carries atimestamp;tool_completed/tool_failedcarry the tool name andduration_ms.
Scheduled jobs — AgentScheduler
- Cron for agents —
sched.add(prompt, every=3600),at="09:00"daily, orcron="0 8 * * 1"(optionalcroniter);run_forever()fires jobs as they come due. - Durable jobs — pass
store=SQLiteJobStore()and due times + run counts persist across restarts; a re-added job resumes its slot instead of resetting. - Production niceties —
on_resultcallbacks,max_runscaps, session-backed runs, and injectableclock/sleepso schedules are unit-testable with zero real waiting.
MCP, deeper — resources, prompts, streamable HTTP
- Resources & prompts —
server.list_resources()/read_resource(uri)andlist_prompts()/get_prompt(name, args);server.resource_tool()gives the model a tool to browse/read a server's resources. Servers that don't implement them return empty lists, not errors. - Streamable HTTP transport —
MCPStreamableHTTPTransportspeaks the 2025 spec revision: JSON and SSE responses,Mcp-Session-Idaffinity, andbearer_token=on both HTTP transports for OAuth-protected servers.
Run metrics & live-updatable events
result.summary()— wall-clock duration, iterations, token usage, and a per-tool breakdown (calls / failures / total ms) in one dict.- Correlation ids —
tool_called/tool_completed/tool_failed/tool_retryshare acall_id, so live UIs can update one tool card in place (running → ✓/✗) instead of appending lines.
Background subagents & context compaction
- Parallel delegation —
sub_agentacceptsbackground=true(returns a task id immediately, runs on a thread pool) andcollect="task-N"to fetch the result — background task fan-out. - Observable compaction — when a run approaches the context window, older
turns are summarized (user/assistant content included, not dropped) and a
context_compactedevent reports before/after message counts.
See the Super agent guide for the full tour.
v1.0.14 — 2026-06-13
The SHIPIT Workspace. Point an agent at a repo and it just works. All opt-in, backward compatible. 1884 tests passing (+30 new). 0 regressions.
Added
- Project memory — agents auto-load
SHIPIT.md/AGENTS.md/.shipit/SHIPIT.md(+ user-global~/.shipit/SHIPIT.md) into the system prompt, with@pathimports. Opt out viaauto_project_memory=False. API:load_project_memory(). - Slash commands —
.shipit/commands/<name>.mdinvoked withagent.run("/<name> args");$ARGUMENTS/$1substitution + frontmatter stripping. API:discover_commands(),expand_command(). - Declarative config —
.shipit/settings.json(permissions + env + model) merged under~/.shipit/settings.json; wires into the permission engine. API:load_settings(),WorkspaceSettings.Agent.for_project(llm=…, project_root=…)loads settings + builtins + memory + commands in one call. TodoTool— live task tracking (the model maintains apending → in_progress → completedchecklist; inAgent.with_builtins()).- Notebooks
67/68+ docs pages.
v1.0.13 — 2026-06-07
Computer-use + adapter fixes. Two bugs that blocked the computer-use agent on every provider, both backward compatible. 1854 tests passing (+10 new). 0 regressions.
Fixed
- Computer-use works in Jupyter / asyncio.
PlaywrightBrowserSessionused the sync Playwright API, which can't run inside a notebook's running asyncio loop. It now runs all Playwright calls on a dedicated loop-free worker thread (same synchronous API). - All LLM adapters accept dict messages — fixes
'dict' object has no attribute 'role'.ComputerUseAgentpasses raw{"role","content"}dicts (sometimes multimodal); the LiteLLM family (Bedrock/Gemini/Vertex/Groq/Together/Ollama) + OpenAI now serialize dicts and translate the Anthropic image block to a portableimage_url; Anthropic + ShipitLLM coerce dicts via a sharedcoerce_message()helper.
v1.0.12 — 2026-06-07
Claude API power + cross-provider caching. Server-side tools, citations, the Batch API, interleaved thinking & context editing — plus prompt caching that works across providers, not just Anthropic. All opt-in, backward compatible. 1844 tests passing. 0 regressions.
Added — cross-provider prompt caching
- Caching is no longer Anthropic-only. The OpenAI adapter now surfaces
usage["cache_read_input_tokens"]from OpenAI's automatic prompt caching (prompt_tokens_details.cached_tokens) — the same keyCostTrackeruses for Anthropic/Bedrock/Vertexcache_control. LiteLLM forwards both shapes. Cache-read cost accounting now spans Anthropic, Bedrock, Vertex, and OpenAI/-compatible providers.
Added — Anthropic server-side tools
shipit_agent.llms.server_tools:web_search(),code_execution(),computer_use(),bash(),text_editor()declarations that run in Anthropic's sandbox (zero local infra); beta headers auto-attached;server_tool_use/results surface inLLMResponse.metadata. Other providers: use shipit's client-side tools, which work with any LLM.
Added — citations & Batch API
- Citation document helpers (
text_document/pdf_document/url_pdf_document) →metadata["citations"];BatchRuntime(shipit_agent.batch) for ~50%-cheaper bulk runs via the Anthropic Batches API.
Added — interleaved thinking & context editing
AnthropicChatLLM(interleaved_thinking=True)(beta) +context_management=server-side context editing.
Added — examples & docs
- Notebooks
64–66and docs pages, each with honest per-feature provider-support notes.
v1.0.11 — 2026-06-07
The control plane. A production-grade safety + performance layer: a rule-based permission engine with modes (incl. read-only plan mode), hooks that can block or rewrite tool calls, prompt caching for ~10× cheaper repeated calls, and a model-driven memory tool. All opt-in and backward compatible. 1795 tests passing (+50 new). 0 regressions.
Added — permissions & plan mode
PermissionEngine— rule-based gate over every tool call (no LLM):allow/deny/askglobs + modesdefault/acceptEdits/plan(read-only) /bypass. Precedence: deny > mode > allow > ask > callback > default.Agent(permission_mode=…, permissions=…, permission_callback=…)andAgent.plan(prompt)(read-only planning). Denied calls emit atool_deniedevent. New exports:PermissionEngine,PermissionResult,PermissionDecision.
Added — blocking / modifying hooks
before_toolhooks can return a decision to deny or rewrite arguments (PermissionResult(..., updated_arguments=…)/{"decision":"deny"}); newon_user_prompthook redacts/rewrites prompts.None= observe-only (backward compatible).
Added — prompt caching
AnthropicChatLLM(prompt_caching=True)/LiteLLMChatLLM(prompt_caching=True)(default on for Claude) placecache_controlon tools + system prompt;usage["cache_read_input_tokens"]/["cache_creation_input_tokens"]flow intoCostTracker(reads ~10% of input). Bedrock inherits via LiteLLM.
Added — memory tool
ClaudeMemoryTool(memory_20250818shape):view/create/str_replace/insert/delete/rename, sandboxed to.shipit_workspace/memories.
Added — examples & docs
- Notebooks
61–63and docs pages for permissions/plan mode, prompt caching, and the memory tool.
v1.0.10 — 2026-06-07
Bug-fix & hardening release. Fixes a v1.0.9 regression that broke custom LLM adapters, hardens local-execution and connector tools against sandbox-escape / SSRF, and tightens session, cost, and concurrency correctness. No public API removed; no caller needs changes. 1742 tests passing (+180 new). 0 regressions.
Fixed — critical
text_delta_callbackregression (v1.0.9) — the runtime passed the new streaming callback toLLM.complete()unconditionally, raisingTypeErrorfor any adapter on the prior signature. It now detects support via signature inspection and only passes it to adapters that accept it (backward compatible; streaming preserved for opted-in adapters).- Multi-turn sessions no longer stack a duplicate system prompt every turn — the runtime injects exactly one leading system message and strips persisted ones on reload (fixes unbounded growth in the
AgentChatSessionpath).
Fixed — security hardening
- Bash tool rejects command substitution (
$(…), backticks), process substitution, and file redirection that could bypass the allowlist. open_urlis http(s)-only and blocksfile://plus private / loopback / link-local / cloud-metadata IPs (SSRF); opt out withallow_private_hosts=True.- SQL tool read-only guard scans the whole statement and rejects stacked statements (closes an
allow_writes=Falsebypass). - OAuth
exchange_code(state=…)validates and consumes the CSRF state nonce. edit_filerefuses non-UTF-8 files instead of corrupting them;FileCredentialStorewarns about plaintext, chmods0600, and writes atomically.
Fixed — reliability & correctness
- MCP transports are closed on error (
try/finally) and on a failed discovery handshake — no leaked subprocesses. - Parallel tools run on isolated state and merge deterministically (race fixed).
- The iteration-cap summary turn is now counted in usage/cost;
CostTrackerflags unknown-model pricing instead of silently billing$0under a budget. JSONParserbalanced-brace extraction; pipelinestream()no longer double-runs steps; autopilot fan-out preserves input order; deep-agent factory forwardsmemory/history/verifier; vector-store ids are monotonic; file stores write atomically; grep gains a timeout; ShipCrew timeout actually pre-empts.
Added
- 180+ new tests and six runnable examples (
examples/13–18).
v1.0.7 — 2026-04-24
Agents for every role. 12 new tools and 9 new persona specialists turn shipit-agent into a framework that ships agents for developers, designers, sales reps, PMs, data analysts, finance, customer support, and recruiters — not just code-slinging agents.
Core Tools — Everyone Benefits
GitHubTool— 16 actions covering issues, pull requests, reviews (APPROVE / REQUEST_CHANGES / COMMENT), file contents, and GitHub Actions workflow runs. github.com + GitHub Enterprise. Rate-limit aware with structuredretry_after_epochpayload.GitLabTool— 16 actions for issues, merge requests, file contents, and CI pipelines. Self-hosted + gitlab.com.SQLTool— SQLAlchemy-backed. Works with PostgreSQL, MySQL, SQLite, BigQuery, Snowflake, Redshift, MSSQL, Oracle. Read-safe by default; mutations gated byallow_writes=True. 46 tests.VisionTool— image → text via any vision-capable LLM (Claude, GPT-4o, Gemini, Bedrock Claude, LiteLLM). Accepts filesystem paths, URLs, data-URLs, or raw base64.PDFTool— extract text, per-page content, metadata from PDFs (local or URL). Page-range parsing, char caps, clean error taxonomy.LangSmithExporter+OpenTelemetryExporter— ship every agent's trace to LangSmith or any OTLP backend (Datadog, Grafana, Honeycomb).
Persona SaaS Connectors
FigmaTool— files, nodes, rendered images, comments, team projects, component libraries.SalesforceTool— SOQL/SOSL queries, accounts/opportunities/contacts, safelog_activity+ gated full writes.StripeTool— customers, charges, subscriptions, invoices, products. Read-heavy by default. Test/live mode detection.GoogleSheetsTool— read/write cells, ranges, formulas, sheet structure. A1-notation with proper URL encoding.ZendeskTool— ticket search/create/update/close,add_commentalways enabled for triage, macro preview.LinkedInSearchTool— strictly read-only. Profile + company lookup + search. Four layers of write-free enforcement.
Nine New Specialist Personas
code-reviewer-bot,release-engineer— GitHub-powered dev ops.figma-designer— design review + handoff via Figma + Vision.sales-rep,account-executive,sales-ops— Salesforce + LinkedIn + SQL.recruiter— sourcing + candidate tracking via LinkedIn + Sheets + PDF.finance-analyst— Stripe + PDF + SQL + dashboard rendering.customer-support-agent— Zendesk + Vision + Slack.
Total specialists in agents.json now 56.
Seven Persona Walk-Through Notebooks
47_pm_pr_digest— nightly PR digest across repos48_designer_figma_review— Figma → design-review dashboard49_sales_lead_enrichment— Salesforce + LinkedIn → personalised outreach50_manager_sheets_kpis— Google Sheets → weekly dashboard51_support_zendesk_triage— ticket triage with screenshot reading52_analyst_sql_to_dashboard— SQL → dashboard (real SQLite)53_finance_stripe_pdf_cashflow— Stripe + PDF contracts → cash-flow one-pager
Each runs clean with 0 cell errors using stubbed API responses — no credentials needed to see the flow.
Tests
286 new tests across 12 new test files. 1190 passing, 8 skipped (gated Bedrock E2E + soak), 0 regressions.
Upgrade
pip install --upgrade shipit-agent==1.0.7No breaking changes. Optional extras for new deps: pip install 'shipit-agent[pdf,sql,otel]'.
v1.0.6 — 2026-04-24
Bulletproof 24-hour Autopilot, AI-driven dashboard renderer, LiteLLM proxy. Autopilot is hardened for multi-day runs: cumulative budgets across resume, SIGTERM-safe shutdown, end-to-end dollar tracking, corrupt-checkpoint quarantine. New DashboardRenderTool lets an agent pick the right section shape (metrics / chart / timeline / cards / phases / verdict) for any one-pager question and emit a self-contained HTML artifact. First-class LiteLLM-proxy support so any company can plug every agent into their own proxy in three fields.
Autopilot — Bulletproof For 24-Hour Runs
- Cumulative budgets across resume — every field of
BudgetUsage(seconds, tool calls, tokens, dollars, iterations) persists in the checkpoint. A run that crashes at hour 12 and resumes for another 12 trips a 24-hour cap exactly at hour 24, not hour 36. - Dollar tracking wired end-to-end —
usage.dollarsaccumulates from LLM response metadata viashipit_agent.costs.pricing, with Bedrock / LiteLLM prefix handling plus a coarse fallback for unpriced models.max_dollarsbudgets actually fire. - Signal-safe shutdown —
SIGTERM/SIGHUPare caught alongsideSIGINT.systemd stop/launchd stophalt cleanly with one final checkpoint.autopilot.request_stop(reason)is a thread-safe external halt for daemons / UIs. - Corrupt-checkpoint quarantine — a JSON parse error during
load()renames the bad file to<run_id>.corrupted.<timestamp>.jsoninstead of silently dropping state. Operators can forensic-inspect later. - First-iteration heartbeat +
remainingpayload on every event — slow first steps never look like hangs; iteration / heartbeat events carry per-axis headroom so UIs can render ETA bars. - Pre-iteration budget projection —
BudgetPolicy.would_exceed_after(...)+BudgetPolicy.remaining(usage)helpers. CheckpointStore.usage_from_payload()— back-compat helper that loads both schema v1 (iterations only) and v2 (fullBudgetUsage) transparently.
Dashboard Render Tool — The Agent Picks The Shape
shipit_agent.tools.dashboard_renderpackage withDashboardRenderTooland arender_dashboard(spec)helper.- The agent composes the dashboard from these section types:
metrics,line_chart,bar_chart,bars,timeline,cards,lifestyle_grid,phases,callout,verdict. - Self-contained HTML output — inline CSS; Chart.js via CDN only when a chart section is present. Renders in any browser or email client.
- Security defaults — all user strings HTML-escaped, colors filtered through a hex allow-list (no CSS injection), path-traversal on
exportneutralised. - Zero-glue artifact flow — tool returns
{'artifact': True, 'kind': 'file', 'name': 'xxx.html', 'content': '...'}, whichArtifactCollector.ingest_tool_metadatapicks up. AnAutopilot(..., artifacts=True)run that calls this tool auto-captures the rendered HTML.
LiteLLM Proxy — Bring Your Own URL + Key
- Three fields (
model,api_base,api_key) point everyAgent,Autopilot, andShipCrewat a self-hosted LiteLLM proxy. - Three equivalent paths to wire it: factory (
build_llm_from_settings), direct class (LiteLLMProxyChatLLM), or purely env vars (SHIPIT_LITELLM_API_BASE+SHIPIT_LITELLM_API_KEY+SHIPIT_LITELLM_MODEL). - Factory auto-detects proxy mode when
api_baseis set; falls back to directLiteLLMChatLLMwhen it isn't. BedrockChatLLMnow only injectsmodify_params=Truefor Anthropic on Bedrock; Nova, Titan, Llama, Mistral, andopenai.gpt-oss-120bon Bedrock work without the prior "extraneous key" rejection.
Python 3.13 + 3.14 Support
- Added
Programming Language :: Python :: 3.13and:: 3.14classifiers topyproject.toml.requires-python = ">=3.11"already let 3.13 / 3.14 installs succeed; the classifiers make the support discoverable on PyPI. - CI matrix expanded to
['3.11', '3.12', '3.13', '3.14']onubuntu-latestandmacos-latest(.github/workflows/test.yml). datetime.utcnow()replaced withdatetime.now(timezone.utc)incosts.tracker.CostRecordandnotifications.base.Notification.utcnow()has been deprecation-warned since 3.12 and will be removed — this is a forward-compatible swap with identical behaviour.
Notebook 46 — Runnable Walk-Through
notebooks/46_dashboard_render_tool_and_litellm.ipynb— pick an LLM (Bedrock / LiteLLM direct / LiteLLM proxy with your URL + key) →render_dashboard(spec)→ Agent with the tool → Autopilot artifact ingest.- Executes clean with 0 cell errors; writes
life_vision.html+finance-one-pager-fy26.htmlundernotebooks/_dashboard_workspace/. - Regenerated via
notebooks/_nb46_builder.py.
Tests — +41 New, All Passing
tests/test_autopilot_hardening.py— 14 tests for full-usage persistence, v1 back-compat, corruption quarantine, dollar tracking (explicit / pricing / disabled), SIGTERM stop, first-iter heartbeat,remainingpayload, pre-iteration projection.tests/test_autopilot_long_task.py— 6 compressed-time simulations (hundreds of iterations, 5-crash resume chain, SIGTERM mid-run, mid-run corruption recovery, 50-child fan-out) + 1 opt-in Bedrock soak gated onSHIPIT_AUTOPILOT_SOAK=<seconds>.tests/test_autopilot_bedrock_e2e.py— 7 real-Bedrock E2E tests (SHIPIT_BEDROCK_E2E=1) covering run, stream, resume-cumulative, dollars, artifacts, critic, fan-out.tests/test_dashboard_render.py— 20 tests across every section type, escaping, color allow-list, chart config, export + traversal guard,ArtifactCollectoringest, and a realistic full-spec life-vision dashboard.tests/test_notebook_assets.py— locks the current notebook-44/45 API usage so the recent fixes can't regress.
Fixed
- A resumed Autopilot previously reset wall-clock, tokens, tool-calls, and dollars to zero — only iteration count survived the checkpoint.
usage.dollarswas never incremented, somax_dollarsbudgets never fired.BedrockChatLLMcould not drive non-Anthropic Bedrock models because the adapter always injectedmodify_params=True.
Upgrade
pip install --upgrade shipit-agent==1.0.6No breaking changes. Checkpoints written by 1.0.5 load transparently via the v1-compat path.
v1.0.5 — 2026-04-18
Prebuilt agents, multi-agent crews, notifications, and cost tracking. 40 ready-to-use agent personas. DAG-based ShipCrew orchestration with sequential, parallel, and hierarchical modes. Slack, Discord, and Telegram notification hub. Real-time cost tracking with budget enforcement. 4 new notebooks and expanded regression coverage across the new APIs.
Prebuilt Agents — 40 Ready-to-Use Personas
shipit_agent.agentsmodule — newAgentDefinitiondataclass andAgentRegistryfor loading, searching, and composing agent personas.- 40 agents across 8 categories: Architecture (5), Code Quality (6), Security (5), DevOps (5), Testing (5), Planning (4), Research (5), Content (5).
AgentRegistry.default()— loads the built-inagents.jsonin one line.- Search & browse —
registry.search("security audit"),registry.list_by_category("Security"),registry.categories(). .shipit/agents/override — drop JSON agent files in your project directory;AgentRegistry.from_directory()loads them,registry.merge()combines with built-ins.AgentDefinition.system_prompt()— assembles role, goal, backstory, and prompt into a structured system prompt with# Role,# Goal,# Background,# Instructionsheaders.- Serialization —
to_dict()(camelCase) andfrom_dict()(accepts both camelCase and snake_case). - Each agent has 1,200–1,800 char prompts with methodology, quality standards, and output format.
ShipCrew — Multi-Agent Crew Orchestration
shipit_agent.deep.ship_crewpackage — newShipCrew,ShipAgent,ShipTask,ShipCoordinator,ShipCrewResultclasses.- DAG-based task dependencies —
ShipTask.depends_onforms a directed acyclic graph. Kahn's algorithm validates no cycles and resolves topological execution order. - Three execution modes:
sequential— tasks run one at a time in topological order.parallel— independent tasks in the same DAG layer run concurrently viaThreadPoolExecutor.hierarchical— coordinator LLM dynamically assigns tasks, reviews output, and can request revisions.
- Template variable resolution —
{output_key}in task descriptions auto-resolves from upstream task outputs._SafeFormatMapensures missing keys don't crash. - Context variables —
crew.run(topic="AI", audience="devs")injects runtime variables into task descriptions. ShipAgent.from_registry()— build crew agents directly from the prebuilt agent registry.create_ship_crew()factory — accepts plain dicts or objects; useful for JSON-driven configuration.- Validation —
crew.validate()checks missing agents, unknown dependencies, and cyclic DAGs before execution. - Streaming —
crew.stream()yieldsAgentEventforrun_started,task_started,task_completed,task_failed,run_completed. - Error types —
ShipCrewError,CyclicDependencyError,MissingAgentError,TaskTimeoutError. - Task features —
max_retries,timeout_seconds,contextdict,output_schemafor structured output. ShipCrewResult—output,task_results(per-task outputs by key),execution_order,failed_tasks,metadata(timing).
Notification Hub — Slack, Discord & Telegram
shipit_agent.notificationspackage — newNotificationManager,Notification,SlackNotifier,DiscordNotifier,TelegramNotifier.- Slack — Block Kit webhooks with color-coded severity bars, metadata fields, and timestamps. Uses
urllib.request— zero external dependencies. - Discord — rich embeds with color-coded severity, inline metadata fields, and footer. Handles 204 responses correctly.
- Telegram — Bot API with MarkdownV2 formatting, auto-escaped special characters, emoji severity indicators.
NotificationManager— dispatch to multiple channels simultaneously. Filter bymin_severityand/oreventslist.manager.as_hooks()— returnsAgentHooksthat auto-notify onrun_started,run_completed,tool_failed. Wire into any agent withhooks=manager.as_hooks("my-agent").- Custom templates — override default message templates per event type.
render_template()uses safe formatting (missing keys stay as{key}). - Severity levels —
info,warning,error,criticalwith numeric ordering for filtering. Notifierprotocol — build custom notifiers (PagerDuty, Teams, SMS) by implementingasync send(notification) -> bool.
Cost Tracking & Budgets
shipit_agent.costspackage — newCostTracker,Budget,BudgetExceededError,CostRecord.MODEL_PRICING— built-in per-million-token pricing for 20+ models: Claude Opus/Sonnet/Haiku 4, GPT-4o/4o-mini/4.1/o3/o4-mini, Gemini 2.5 Pro/Flash, Llama 4 Scout/Maverick, Bedrock model IDs. Includes cache read/write pricing for Anthropic.MODEL_ALIASES— short names:"opus"→"claude-opus-4","sonnet"→"claude-sonnet-4", etc.CostTracker.record_call()— records an LLM call, computes USD cost, checks budget, and returns aCostRecord.Budget(max_dollars=5.00, warn_at=0.80)— budget enforcement.BudgetExceededErrorraised when exceeded;on_cost_alertcallback at warning threshold.tracker.as_hooks()— returnsAgentHooksfor automatic per-call cost tracking. Extracts usage from Anthropic, OpenAI, and Bedrock response objects.tracker.breakdown()— per-call cost attribution.tracker.summary()— full report with totals, budget status, and per-call details.tracker.add_model()— register custom model pricing at runtime.
Notebooks
- Notebook 32 — Prebuilt Agents (27 cells): registry loading, category browsing, search, category statistics, agent inspection, live agent construction, multi-category showcase, serialization, custom definitions, registry merging,
.shipit/agents/override, ShipCrew integration. - Notebook 33 — ShipCrew Orchestration (28 cells): basic crew, diamond DAG, parallel mode, context variables, hierarchical LLM-driven mode, streaming events, from registry, factory, validation/errors, ShipTask advanced features, crew + cost tracking.
- Notebook 34 — Notifications (27 cells): notification data model, all severity levels, Slack Block Kit, Discord embeds, Telegram MarkdownV2, severity comparison, production event examples, multi-channel dispatch, severity/event filtering, real agent demo, custom templates, cost alert integration.
- Notebook 35 — Cost Tracking & Budgets (31 cells): pricing table, model comparison, cache savings calculator, per-call tracking, budget enforcement, warning callbacks, breakdown, summary, custom pricing, auto-hooks, streaming + live cost, multi-model tracking.
Tests
- Expanded regression coverage across the new surfaces:
test_prebuilt_agents.py(39 tests): AgentDefinition serialization, system prompt assembly, AgentRegistry loading/search/merge/categories, data integrity validation for all 40 agents.test_ship_crew.py(44 tests): ShipTask resolution/serialization includingoutput_schema, ShipAgent construction/delegation/from_registry, ShipCoordinator DAG building/cycle detection/sequential/parallel/hierarchical execution, ShipCrew validation/run/stream/context variables, create_ship_crew factory, error inheritance.test_notifications_and_costs.py(76 tests): Notification model/serialization, severity ordering, template rendering, SlackNotifier Block Kit/send, DiscordNotifier embeds/send, TelegramNotifier MarkdownV2/escaping/send, NotificationManager dispatch/filtering/hooks/custom templates, Budget warn/exceed, BudgetExceededError, CostTracker pricing/recording/breakdown/summary/budget/warnings/hooks/reset, usage/model extraction, MODEL_PRICING completeness, alias resolution.
New Files
shipit_agent/agents/__init__.py
shipit_agent/agents/definition.py
shipit_agent/agents/registry.py
shipit_agent/agents/agents.json (40 agent definitions)
shipit_agent/deep/ship_crew/__init__.py
shipit_agent/deep/ship_crew/agent.py
shipit_agent/deep/ship_crew/coordinator.py
shipit_agent/deep/ship_crew/crew.py
shipit_agent/deep/ship_crew/errors.py
shipit_agent/deep/ship_crew/result.py
shipit_agent/deep/ship_crew/task.py
shipit_agent/notifications/__init__.py
shipit_agent/notifications/base.py
shipit_agent/notifications/discord.py
shipit_agent/notifications/manager.py
shipit_agent/notifications/slack.py
shipit_agent/notifications/telegram.py
shipit_agent/notifications/templates.py
shipit_agent/costs/__init__.py
shipit_agent/costs/budget.py
shipit_agent/costs/pricing.py
shipit_agent/costs/tracker.py
tests/test_prebuilt_agents.py
tests/test_ship_crew.py
tests/test_notifications_and_costs.py
notebooks/32_prebuilt_agents.ipynb
notebooks/33_ship_crew_orchestration.ipynb
notebooks/34_notifications.ipynb
notebooks/35_cost_tracking_and_budgets.ipynbv1.0.4 — 2026-04-12
Skills, tools, and runtime power-up. All 32 tool prompts rewritten with decision trees and anti-patterns. Full skill-to-tool linking for all 37 packaged skills. Automatic iteration boost for skill-driven workflows. Expanded bash allowlist (50+ commands). Streaming, chat, and project-building examples across 3 notebooks. Comprehensive docstrings across every key module. 32 skill tests. All passing.
Skills — Full Tool Linking
- 37 skill tool bundles (up from 10) — every packaged skill now declares the built-in tools it needs. When a skill is selected, the agent auto-attaches the right tools.
- Shared tool groups (
_FILE_CORE,_CODE_CORE,_WEB_CORE) reduce duplication across bundles. validate_tool_bundles()— new helper that checks every tool name inSKILL_TOOL_BUNDLESagainst the real builtin map.
Agent — Iteration Boost & Efficiency
_effective_max_iterations()— auto-boosts 4 → 8 when skills inject extra tools so skill-driven workflows can complete without cutting off early.- Single skill computation —
run()andstream()now compute skills once and reuse (previously 3x per call).
Tool Prompts — All 32 Upgraded
Every tool's prompt.py rewritten with decision trees, anti-patterns, workflow guidance, and cross-tool coordination.
Bash Allowlist Expansion
- 50+ safe commands added:
mkdir,touch,cp,mv,echo,grep,curl,docker,kubectl,terraform,aws,go,cargo,npx,tsc,eslint,black,isort,tree,awk,cut,diff, and more.
Documentation
- Comprehensive docstrings on
agent.py,builtins.py,skills/loader.py,skills/registry.py,skills/tool_bundles.py,deep_agent/factory.py. - 6 tool doc pages updated with enhanced prompts.
- Skills guide expanded with 7 real-world examples, streaming sections, chat sessions, and event type reference.
- Notebook 27 rewritten (38 cells): streaming, chat streaming, project build, web scraping, DeepAgent chat.
- Notebook 29 (new): DeepAgent + skills + memory + verify + reflect + sub-agents + streaming.
- Notebook 30 (new): real-world full project build across 6 steps with 5 different skills.
Tests
- 15 new tests (17 → 32 total): iteration boost, bundle validation, chat sessions, streaming, chat streaming, memory + skills, DeepAgent chat/stream.
v1.0.3 — 2026-04-11
Major feature release. Super RAG subsystem, DeepAgent factory (verify / reflect / goal / sub-agents), live multi-agent chat REPL (shipit chat), Agent memory cookbook, plus deep docs + notebook coverage. 521 unit tests. 19 Bedrock end-to-end smoke tests. All passing.
Super RAG
shipit_agent.ragsubsystem — pluggable chunker + embedder + vector store + keyword store + hybrid pipeline (vector + BM25 + RRF + recency bias + rerank + context expansion).rag=on every agent type — auto-wiresrag_search/rag_fetch_chunk/rag_list_sourcestools, augments the system prompt with citation instructions, and attachesresult.rag_sourceswith stable[N]citation indices.- Adapters —
DrkCacheVectorStore(pgvector over psycopg2) + lazy Chroma / Qdrant / pgvector. - Thread-local per-run source tracker so concurrent runs never leak citations.
DeepAgent
shipit_agent.deep.DeepAgent— power-user factory bundling seven deep tools:plan_task,decompose_problem,workspace_files,sub_agent,synthesize_evidence,decision_matrix,verify_output. Guide- One-flag power features:
verify=True,reflect=True,goal=Goal(...),rag=RAG(...),memory=AgentMemory(...). agents=sub-agent delegation — plug any mix of agent types as named delegates via a built-indelegate_to_agenttool.create_deep_agent()functional helper — auto-wraps plain Python callables as tools.- Nested event streaming — sub-agent events surface inside
tool_completed.metadata['events'].
Live chat REPL
shipit chat— modern multi-agent terminal REPL. Switch agent types live, index files mid-session, save/load conversations, togglereflect/verify, inspect tools and sources. Guide- Rich slash commands:
/agent,/agents,/tools,/sources,/index,/rag,/goal,/reflect,/verify,/history,/save,/load,/reset,/info, … - Pluggable LLM provider via
--provider; persistent sessions via--session-dir.
Streaming
DeepAgent.stream()covers every execution mode (direct, verified, reflective, goal-driven, sub-agent delegation).PersistentAgent.stream()added with per-step checkpointing.rag_sourcesevent type added — emitted after every RAG-backed run.
Memory
- Dedicated Agent → Memory cookbook explaining the two memory systems (
memory_store=for the LLM'smemorytool vsAgentMemoryfor application-curated profiles). Guide - DeepAgent auto-hydration —
memory=AgentMemory(...)seeds the inner agent'shistoryfrom the conversation summary. - Notebook 26 — runnable end-to-end tour.
Docs
- New Agent section (6 pages): Overview, Examples, Streaming, With RAG, With Tools, Memory, Sessions.
- New Super RAG section (6 pages): Overview, Standalone, Files & Chunks, With Agent, With Deep Agents, Adapters, API.
- New DeepAgent page. Reference
- Parameters Reference — every constructor parameter for every agent type and key class. Reference
- Updated Architecture + Model Adapters reference pages.
- Updated quickstart with Agent / Deep Agent / RAG sections.
- Updated FAQ with "Agent types — which one should I use?".
- 5 new notebooks (22–26): RAG basics, RAG + Agent, RAG + Deep Agents, DeepAgent chat, Agent memory.
- Full-width docs layout + collapsible TOC with floating toggle, persistence via localStorage.
Build
shipit-chatscript entry point.- Granular extras:
rag,rag-openai,rag-cohere,rag-chroma,rag-qdrant,rag-pgvector,rag-drk-cache,rag-pdf,rag-docx,rag-rerank-cohere,rag-rerank-cross-encoder, plusbedrock,google,groq,together,ollama. Theallextra bundles everything.
Fixed
- Tool schema format bug —
RAGSearchTool,RAGFetchChunkTool,RAGListSourcesTool,WebhookPayloadToolnow use the wrapped{"type": "function", "function": {...}}shape. Previously they were returning flat dicts and Bedrock's Converse API was rejecting them with empty-name validation errors. New regression test scans every tool for Bedrock compatibility. memory=AgentMemorytype coercion —DeepAgentandGoalAgentno longer auto-assignAgentMemory.knowledge(aSemanticMemory) intomemory_store=(which expects aMemoryStore).memory=now only seedshistory; users passmemory_store=explicitly for the runtime'smemorytool.Agent.with_builtins(tools=[...])keyword collision — the method now accepts and merges usertools=with the builtin catalogue (last-write-wins on name collision).AgentDelegationToolstreaming — uses inner agent'sstream()and packs events intotool_completed.metadata['events'].
Test coverage
- 521 unit tests (up from 285) — green.
- 19 end-to-end Bedrock smoke tests in
scripts/smoke_bedrock_e2e.pycover every public surface end-to-end against real Bedrock.
v1.0.2 — 2026-04-10
Major feature release. Deep agents, structured output, pipelines, agent teams, advanced memory, output parsers, and runtime power features. 285 tests. 12 examples. 8 notebooks. 13 new doc pages.
Deep Agents
- GoalAgent — Autonomous goal decomposition with success criteria, streaming, and
.with_builtins(). Guide - ReflectiveAgent — Self-evaluation with quality scores and revision loop. Guide
- Supervisor / Worker — Hierarchical delegation with quality review. Guide
- AdaptiveAgent — Runtime tool creation from Python code. Guide
- PersistentAgent — Checkpoint and resume across sessions. Guide
- Channel / AgentMessage — Typed agent-to-agent communication. Guide
- AgentBenchmark — Systematic agent testing framework. Guide
- Deep Agents API Reference — Full constructor, method, and return type docs. Reference
Structured Output & Parsers
output_schemaon Agent.run() — Pydantic models + JSON schemas. Guide- JSONParser, PydanticParser, RegexParser, MarkdownParser. Guide
Composition
- Pipeline — Sequential, parallel, conditional, function steps, streaming. Guide
- AgentTeam — LLM-routed multi-agent coordination with streaming. Guide
Advanced Memory
- ConversationMemory — buffer/window/summary/token strategies. Guide
- SemanticMemory — Embedding-based vector search. Guide
- EntityMemory — Track people, projects, concepts. Guide
- AgentMemory — Unified interface with
.default(). Guide
Runtime Power Features
- Parallel tool execution. Guide
- Graceful tool failure. Guide
- Context window management. Guide
- Hooks & middleware. Guide
- Mid-run re-planning. Guide
- Async runtime. Guide
- Transient error auto-retry (429/500/503).
Changed
- Selective memory storage (breaking) — Only
persist=Truetool results stored. - Safer retry defaults —
(ConnectionError, TimeoutError, OSError)instead of(Exception,).
v1.0.1 — 2026-04-09
Maintenance release. Bug fix in the tool runner plus repo hygiene, contributor experience, and CI hardening. Strongly recommended upgrade from 1.0.0 if you use Bedrock gpt-oss-120b.
Fixed
ToolRunnerargument collision — FixedTypeError: got multiple values for argument 'context'when an LLM (notablybedrock/openai.gpt-oss-120b-1:0) emitscontextas a tool-call argument. The runner now strips reserved argument names (context,self) from tool-call arguments before forwarding. Affects every built-in tool.
Added
CHANGELOG.mdat repo root in Keep a Changelog formatCONTRIBUTING.mdwith dev setup, commit conventions, PR checklist, and "how to add a new LLM adapter / tool" guides- GitHub issue templates — structured bug report, feature request, and config forms
- PR template with 12-item verification checklist
- Test CI —
pytest -qon Python 3.11 + 3.12 × Ubuntu + macOS (4 matrix cells), with smoke-test of all 11 LLM adapter imports - Gitleaks secret scanning CI with SARIF upload to GitHub Security tab, inline PR comments, Actions summary
- Pre-commit hooks — trailing whitespace, EOF fixer, YAML/TOML validation, gitleaks v8.21.2, ruff lint + format
- Gitleaks allowlist for runtime tool outputs (scraped HTML contains false-positive "API keys" like Pushly domainKeys)
Changed
.gitignorerewritten to dedupe entries and cover all runtime directories (site/,.eggs/,pip-wheel-metadata/)- Runtime tool outputs untracked from git (
sessions/,traces/,memory.json,.shipit_notebooks/**) — they were accidentally committed in 1.0.0
Security
- Added CI and pre-commit secret scanning to prevent future credential leaks
- No runtime code changed —
shipit_agent/module is byte-identical to 1.0.0
v1.0.0 — 2026-04-09
First stable release. Focused on making the agent loop observable, interchangeable, and out of the way.
🧠 Live reasoning / thinking events
LLMResponse.reasoning_contentfield added to carry thinking/reasoning blocks from any provider- New
_extract_reasoning()helper handles three shapes:- Flat
reasoning_contenton the response message (OpenAI o-series,gpt-oss, DeepSeek R1, Anthropic via LiteLLM) - Anthropic
thinking_blocks[*].thinking(Claude extended thinking) model_dump()fallback for pydantic dumps
- Flat
- Runtime emits
reasoning_started+reasoning_completedevents whenever reasoning content is non-empty - All three LLM adapters —
OpenAIChatLLM,AnthropicChatLLM,LiteLLMChatLLM/BedrockChatLLM— share the extraction helper OpenAIChatLLMauto-passesreasoning_effort="medium"for reasoning-capable models (o1*,o3*,o4*,gpt-5*,deepseek-r1*)AnthropicChatLLMsupportsthinking_budget_tokens=Nto enable Claude extended thinking
⚡ Truly incremental streaming
agent.stream()now runs the agent on a background daemon thread- Events are pushed through a thread-safe
queue.Queueas they're emitted - Consumer loop yields events the instant they happen — no buffering, no batched delivery
- Worker exceptions are captured and re-raised on the consumer thread
- Works in Jupyter, VS Code, JupyterLab, WebSocket/SSE transports, and plain terminals
🛡️ Bulletproof Bedrock tool pairing
- Planner output is now injected as a
user-role context message rather than an orphanrole="tool"message — fixes Bedrock's "number of toolResult blocks exceeds number of toolUse blocks" error - Every
response.tool_callsentry gets a tool-result message unconditionally:- Success → real tool-result
- Retry → retries first, then final result or error
- Unknown tool → synthetic
"Error: tool X is not registered"tool-result
- Stable
call_{iteration}_{index}tool_call_ids round-trip through message metadata - Multi-iteration tool loops on Bedrock Claude, gpt-oss, and Anthropic native now work without
modify_paramsband-aids
🔑 Zero-friction provider switching
build_llm_from_env()walks upward from CWD to discover.env, so notebooks and scripts work regardless of where they're launched from- Seven providers:
openai,anthropic,bedrock,gemini,vertex,groq,together,ollama, plus a genericlitellmprovider - Per-provider credential validation with clear error messages
SHIPIT_OPENAI_TOOL_CHOICE=requiredenv var to force tool use on lazy models likegpt-4o-mini
🌐 In-process Playwright for open_url
OpenURLToolnow uses Playwright's sync Chromium directly (headless, realistic desktop Chrome UA, 1280×800 viewport)- Handles JS-rendered pages, anti-bot 503s, modern TLS/ALPN
- Stdlib
urllibfallback when Playwright is not installed — zero third-party HTTP dependencies in the core fallback path - Errors never raise out of the tool: they return as
ToolOutputwith awarningslist in metadata - Rich metadata:
fetch_method,status_code,final_url,title
🔍 Upgraded ToolSearchTool
- Replaced binary substring match with drk_cache-style fuzzy scoring:
SequenceMatcher.ratio() + 0.12 × token_hits - Configurable
limitparameter, clamped to[1, max_limit] - New init kwargs:
max_limit,default_limit,token_bonus - Structured error output for empty queries
- Ranked output with scores and "when to use" hints from
prompt_instructions - Noise filter: results below
score=0.05dropped
🪵 Full event taxonomy
14 distinct event types with documented payloads:
run_started, mcp_attached, planning_started, planning_completed, step_started, reasoning_started, reasoning_completed, tool_called, tool_completed, tool_retry, tool_failed, llm_retry, interactive_request, run_completed
🔁 Iteration-cap summarization fallback
- If the model is still calling tools when
max_iterationsis reached, the runtime gives it one more turn withtools=[]to force a natural-language summary run_completedis never empty for normal runs- Guarded with try/except so summarization failures can't mask the rest of the run
Other changes
pyproject.toml:[project.urls]now points to correct GitHub org, addsDocumentationandChangeloglinks.env.example: expanded with all new env vars documentednotebooks/04_agent_streaming_packets.ipynb: full rewrite with .env loading, credential visibility printer, and live Markdown updatesREADME.md: new v1.0 release section with 8 headline features- Full MkDocs Material documentation site at docs.shipiit.com
Breaking changes
None — this is the first stable release. Subsequent 1.x releases will maintain backward compatibility within the 1.x line.