Load context
Resume the durable session, retrieve memory, and activate only the project skills relevant to this task.
Tools, memory, skills, connections, schedules and delegation are arguments — not a framework you assemble. Start with two lines and add only what the job asks for.
from shipit_agent import Agent agent = Agent.for_project( llm=llm, project_root=".", optimized=True,) agent.connect_mcp("github")agent.run_live(task)4 tools · 1 MCP server · budget $0.18
The main Agent owns the lifecycle from context assembly to tool execution, permission checks, verification, tracing, and durable continuation. Every phase is observable and replaceable.
Resume the durable session, retrieve memory, and activate only the project skills relevant to this task.
Turn the request into bounded work, set the tool budget, and decide what can execute concurrently.
Search, read, edit, browse, call MCP servers, or delegate while streaming every event.
Apply path boundaries, guardrails, output redaction, and human approval before sensitive actions.
Run quality gates, return structured evidence, then checkpoint the session for the next turn.
Use the same event stream in a terminal, notebook, API, or chat UI. Switch providers without rewriting your tools, sessions, permissions, or application shell.
Make our retry backoff jittered, preserve existing behavior, and run the relevant tests.
I’ll inspect the retry path and its tests before editing.
pattern="retry|backoff" · 6 matches
src/client/retry.py · +14 −6
42 passed · 0 failed · 1.8s
Implemented full-jitter backoff with the existing cap preserved. Added deterministic tests for the jitter bounds. All 42 retry tests pass.
from shipit_agent import Agentfrom shipit_agent.llms import OpenAIChatLLMllm = OpenAIChatLLM(model="gpt-5")agent = Agent.for_project(llm=llm, project_root=".", optimized=True,)agent.run_live("Make retry backoff jittered and verify it")
`run_live()` renders directly in a terminal. `stream()` and `astream()` expose the same lifecycle events for notebooks, APIs, and custom interfaces.
17 complete, copyable programs using the same Agent API — each with the live output it prints. Move from a minimal reply to files & images, project tools, durable memory, MCP servers, live streaming, specialist delegation, guardrails, and verified operation one capability at a time.
Two lines. Everything else is opt-in.
from shipit_agent import Agentfrom shipit_agent.llms import LiteLLM# One class, any provider — swap the string, keep the code.agent = Agent(llm=LiteLLM("anthropic/claude-opus-4-6"))print(agent.run("Explain what a vector index is, briefly.").output)
No config file, no registry, no builder. An agent with no tools is a chat completion with a memory of the turn — on any provider you point it at.
stream() yields each event the instant it fires — no buffering. Render the tool calls, the reasoning and the answer live in a terminal, a notebook, an API, or a chat UI.
run_started — the run begins, with the promptstep_started — a new LLM iterationreasoning_started / _completed — a thinking block opens and closesfinal_answer — the answer is ready (fires just before close)run_completed — the run is over, with output + usagerun_cancelled — the run was cancelled cooperativelyThe full schema for every event is in the Event Types reference.
No builders, no config file. Everything the agent can do is a keyword argument on Agent(...) — grouped here so you can see the whole surface at once.
llmLLM= requiredThe model client used for every completion. Any provider — swap the string, keep the code.
promptstr= DEFAULT_AGENT_PROMPTThe system prompt. Override for a persona or domain framing.
namestr= "shipit"Agent identifier, surfaced in events and traces.
descriptionstr= ""Free-form description used in traces and supervisor delegation.
metadatadict= {}Arbitrary metadata attached to every event — request id, user id, tags.
max_iterationsint= 12Hard cap on LLM iterations per run. Lower to fail fast, raise for deep reasoning.
The full table, with every field, lives in the Parameters reference.
Point an agent at a project and it behaves like a coding agent: it greps before it edits, follows the conventions committed beside the code, and cannot reach outside the root you gave it.
from shipit_agent import Agent, discover_project_skillsfrom shipit_agent.builtins import get_builtin_toolsROOT = "/srv/platform"agent = Agent(llm=llm,project_root=ROOT, # the boundarytools=get_builtin_tools(llm=llm, project_root=ROOT),skills=discover_project_skills(ROOT), # skills/*.md in the repomemory=True,)agent.run("Find where we retry on 429 and make the backoff jittered. ""Follow the conventions in skills/.")
/srv/platform/http/client.pyinside the root/srv/platform/skills/backoff.mdconventions, from the repo/srv/platform/../billing/.envresolves outside — refused~/.aws/credentialsoutside the root — refused/etc/shadowoutside the root — refusedFile tools resolve paths against it and refuse to leave it. An agent pointed at one service cannot read another's secrets by walking up the tree — not by policy, by construction.
Slack, Gmail, Drive, GitHub, Jira and a dozen more. The credential is resolved from the run's store at call time, never baked into a tool — which is what lets the agent act as the member who asked.
from shipit_agent import Agentfrom shipit_agent.integrations import (CredentialRecord, InMemoryCredentialStore,)store = InMemoryCredentialStore()store.set(CredentialRecord(key="slack", provider="slack",secrets={"token": os.environ["SLACK_TOKEN"]},))agent = Agent(llm=llm, tools=tools, credential_store=store)
The store holds the credential; the tool holds nothing. Swap the store and the same agent acts as somebody else.
A member's own grant shadows the organization's shared one, so messages come from them and revoking theirs touches nobody else.
Secrets go in and never come back out — the API returns a mask. A token is readable by the tool that needs it and by nothing else.
A connection is tested against the provider before it is trusted, and a timeout is reported as inconclusive rather than as a bad credential.
Jobs used to share one process-wide agent, so hourly triage on a cheap model and a nightly audit on a good one could not both exist. Each job now carries its own model, tools, connections, permissions and project root.
scheduler.add("Summarise anything in the inbox that needs a reply.",every=3600,agent_config=ScheduledAgentConfig(model="gpt-5-mini", # 24 runs a day: cheap winsconnections=["gmail"],permission_mode="plan", # reads, never sendsmax_iterations=8,),)
A job that runs 24 times a day should not use the model you would pick for one that runs once.
These run with nobody watching, which is exactly when the design has to hold. Three behaviours worth knowing before you leave one running.
Jobs live in SQLite and survive a restart. A database written by an older version gains the new columns when it is opened — an upgrade is not a migration you have to run.
A job whose provider disappears fails identically every interval. After five in a row it is paused rather than deleted: the configuration is intact, and resuming clears the count so the history that paused it cannot pause it again.
A job that raises does not stop the daemon or the jobs behind it. Its error is recorded on the row, the loop continues, and the next tick is unaffected.
# From the CLI, without writing any Python.shipit jobs add "Summarise the inbox" --every 3600 \--model gpt-5-mini --connection gmail --permission planshipit jobs list # cadence, model, last run, last errorshipit jobs pause inbox # keeps the configshipit jobs resume inbox # clears the failure countshipit jobs start # run the daemon
Start with a two-line agent, then add project boundaries, MCP, memory, approvals, specialists, and schedules without replacing the core loop.