The agent · v1.7.1

One object.Everything it needs.

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.

Runs on any provider
OpenAIAnthropicBedrockGeminiOllamaGroqTogether
Toolsread_file, web_search, github
MemoryEvents, history, facts
SkillsPlanning, reasoning, action
ConnectionsGitHub, Slack, Docs
9
LLM providers
30+
Built-in tools
2,000+
Verified tests
MIT
Self-hosted
Watch a run finish
shipit agent
running

Live executionrun_7fc1
0 / 4 steps0.0s elapsed
1 tools called1 MCP serverbudget $0.00
Mapped the repository184 files indexed · project skills loaded
Connected tools + MCPfilesystem · shell · GitHub · Playwright
Implemented the change3 files edited · permission policy enforced
Verified before returningruff · typecheck · 3189 tests passed
Agent setup
from shipit_agent import Agent
 
agent = Agent.for_project(
llm=llm,
project_root=".",
optimized=True,
)
 
agent.connect_mcp("github")
agent.run_live(task)
Verified and ready

4 tools · 1 MCP server · budget $0.18

MCP connectedpermission mode · askcontext · 0%
The runtime loop

One request.A controlled system.

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.

01

Load context

Resume the durable session, retrieve memory, and activate only the project skills relevant to this task.

session · memory · skills
02

Plan the run

Turn the request into bounded work, set the tool budget, and decide what can execute concurrently.

plan · budget · routing
03

Act with tools

Search, read, edit, browse, call MCP servers, or delegate while streaming every event.

tools · MCP · delegation
04

Enforce control

Apply path boundaries, guardrails, output redaction, and human approval before sensitive actions.

policy · approval · safety
05

Verify + persist

Run quality gates, return structured evidence, then checkpoint the session for the next turn.

tests · trace · checkpoint
Sync + async runtimesbounded concurrencystreamed lifecycle eventsdurable by design
Live agent UI

See the work.Copy the setup.

Use the same event stream in a terminal, notebook, API, or chat UI. Switch providers without rewriting your tools, sessions, permissions, or application shell.

ShipIt project agent
durable session · repo-main
live

Make our retry backoff jittered, preserve existing behavior, and run the relevant tests.

reasoning

I’ll inspect the retry path and its tests before editing.

grep_files

pattern="retry|backoff" · 6 matches

edit_file

src/client/retry.py · +14 −6

run_tests

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.

openai_agent.py
from shipit_agent import Agent
from shipit_agent.llms import OpenAIChatLLM
llm = OpenAIChatLLM(model="gpt-5")
agent = Agent.for_project(
llm=llm, project_root=".", optimized=True,
)
agent.run_live("Make retry backoff jittered and verify it")
One stream, every surface

`run_live()` renders directly in a terminal. `stream()` and `astream()` expose the same lifecycle events for notebooks, APIs, and custom interfaces.

Progressive API

Start with two lines.Scale without rewiring.

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.

same Agent classno buildersno config fileopt-in only
Choose a build01 / 17
Active pattern

The smallest agent

Two lines. Everything else is opt-in.

complete Python file
minimal.py
from shipit_agent import Agent
from 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)
what it prints, live
minimal.py → output
Live

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.

Streamed lifecycle

Every event, as it happens.

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.

agent.stream()
0/15
Live
Lifecycle
  • run_startedthe run begins, with the prompt
  • step_starteda new LLM iteration
  • reasoning_started / _completeda thinking block opens and closes
  • final_answerthe answer is ready (fires just before close)
  • run_completedthe run is over, with output + usage
  • run_cancelledthe run was cancelled cooperatively

The full schema for every event is in the Event Types reference.

Full Agent API

Every argument, one constructor.

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= required

The model client used for every completion. Any provider — swap the string, keep the code.

promptstr= DEFAULT_AGENT_PROMPT

The 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= 12

Hard 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.

Projects

It knows the repository it is in.

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.

project_agent.py
from shipit_agent import Agent, discover_project_skills
from shipit_agent.builtins import get_builtin_tools
ROOT = "/srv/platform"
agent = Agent(
llm=llm,
project_root=ROOT, # the boundary
tools=get_builtin_tools(llm=llm, project_root=ROOT),
skills=discover_project_skills(ROOT), # skills/*.md in the repo
memory=True,
)
agent.run(
"Find where we retry on 429 and make the backoff jittered. "
"Follow the conventions in skills/."
)
One run, in order
grep_filesread
retry|backoff across the repo
read_fileread
http/client.py, lines 40-120
edit_filewrite
jitter added to the backoff
bashverify
pytest tests/test_http.py -q
reportdone
one changed file, tests green
What it may openproject_root=/srv/platform
  • /srv/platform/http/client.py
  • /srv/platform/skills/backoff.md
  • /srv/platform/../billing/.env
  • ~/.aws/credentials
  • /etc/shadow

The root is a boundary

File 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.

Connections

Real services, as a real person.

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.

connect.py
from shipit_agent import Agent
from 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.

As whoever asked

A member's own grant shadows the organization's shared one, so messages come from them and revoking theirs touches nobody else.

Encrypted, write-only

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.

Proved, not assumed

A connection is tested against the provider before it is trusted, and a timeout is reported as inconclusive rather than as a bad credential.

Scheduled operation

Every job gets its own agent.

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.

inbox.py
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 wins
connections=["gmail"],
permission_mode="plan", # reads, never sends
max_iterations=8,
),
)

A job that runs 24 times a day should not use the model you would pick for one that runs once.

Unattended

What happens at 3am.

These run with nobody watching, which is exactly when the design has to hold. Three behaviours worth knowing before you leave one running.

Durable, and it migrates itself

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 broken job pauses

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.

One failure stays one failure

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.

operate.sh
# From the CLI, without writing any Python.
shipit jobs add "Summarise the inbox" --every 3600 \
--model gpt-5-mini --connection gmail --permission plan
shipit jobs list # cadence, model, last run, last error
shipit jobs pause inbox # keeps the config
shipit jobs resume inbox # clears the failure count
shipit jobs start # run the daemon
Build on the main Agent

Your model. Your tools.One observable runtime.

Start with a two-line agent, then add project boundaries, MCP, memory, approvals, specialists, and schedules without replacing the core loop.