# InnerLoop
Python SDK for invoking the [OpenCode](https://opencode.ai/) coding CLI in a headless, non-interactive manner.
Supports:
- Synchronous and Asynchronous modes
- Structured outputs using Pydantic models
- Sessions for multiple invocations with shared context
- Permission configuration for reading, writing, web, and bash tools
- Configurable working directory per loop, session, or call
## Quick Start
See [docs/guides/installing-opencode.md](docs/guides/installing-opencode.md) for installation and configuration.
Quick install:
```bash
make install_opencode # Handles bun config automatically
```
Or manually:
```bash
npm install -g opencode-ai
export OPENCODE_API_KEY=your-key
opencode models
```
## Installation
You can install `innerloop` using `uv` (recommended) or `pip`.
```bash
# Using uv
uv pip install innerloop
# Using pip
pip install innerloop
```
## Prerequisites
**InnerLoop requires the OpenCode CLI.** Install it and ensure `opencode` is on your PATH:
```bash
opencode --version
```
If the command fails, install OpenCode from [opencode.ai](https://opencode.ai/) and add it to your PATH.
See the [OpenCode documentation](https://opencode.ai/docs/) for installation instructions.
## Usage
<!-- BEGIN USAGE -->
We summarize results below each snippet without `print()` in the examples.
Summary shows: Output (first 100 chars), Duration (ms), Events.
To render yourself from a Response object:
```python
def show(resp):
print('Output:', str(resp.output)[:100])
dur = (resp.time.end - resp.time.start) if resp.time else 0
print(f'Duration: {dur} ms')
print(f'Events: {resp.event_count}')
print(resp.model_dump_json(by_alias=True, indent=4))
```
See: src/innerloop/response.py
### Synchronous Run
```python
from innerloop import Loop
loop = Loop(model="anthropic/claude-haiku-4-5")
response = loop.run("Say hello, one short line.")
```
```text
Output: Hello! How can I help you with your code today?
Duration: 3203 ms
Events: 3
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c3ddbaffe1xdYfKWI7bvq3I",
"input": "Say hello, one short line.",
"output": "Hello! How can I help you with your code today?",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947166821,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947166943,
"type": "text",
"text": "Hello! How can I help you with your code today?"
},
{
"seq": 3,
"timestamp": 1761947166991,
"type": "step_finish"
}
],
"time": {
"start": 1761947165017,
"end": 1761947168220
},
"event_count": 3
}
```
</details>
### Asynchronous Run
```python
import asyncio
from innerloop import Loop
async def main():
loop = Loop(model="anthropic/claude-haiku-4-5")
async with loop.asession() as s:
await s("Remember this number: 42")
response = await s("What was the number?")
asyncio.run(main())
```
```text
Output: The number was 42.
Duration: 4165 ms
Events: 3
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c3d139ffeguE8rhOWW70ST2",
"input": "What was the number?",
"output": "The number was 42.",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947173793,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947173971,
"type": "text",
"text": "The number was 42."
},
{
"seq": 3,
"timestamp": 1761947174039,
"type": "step_finish"
}
],
"time": {
"start": 1761947172214,
"end": 1761947176379
},
"event_count": 3
}
```
</details>
### Tool Use (with workdir)
```python
from innerloop import Loop, allow
loop = Loop(
model="anthropic/claude-haiku-4-5",
perms=allow("bash"),
)
loop.default_workdir = "src/innerloop"
response = loop.run("Use bash: ls -1\nReturn only the raw command output.")
```
```text
Output: ```
__init__.py
__pycache__
api.py
config.py
errors.py
events.py
helper.py
invoke.py
mcp.py
output.…
Duration: 4802 ms
Events: 6
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c3b15cffecPu1g9yj32ee7j",
"input": "Use bash: ls -1\nReturn only the raw command output.",
"output": "```\n__init__.py\n__pycache__\napi.py\nconfig.py\nerrors.py\nevents.py\nhelper.py\ninvoke.py\nmcp.py\noutput.py\npermissions.py\nproc.py\nproviders.py\nrequest.py\nresponse.py\nstructured.py\nusage.py\n```",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947178427,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947178973,
"type": "tool_use",
"output": "__init__.py\n__pycache__\napi.py\nconfig.py\nerrors.py\nevents.py\nhelper.py\ninvoke.py\nmcp.py\noutput.py\npe… (truncated)",
"status": "completed",
"tool": "bash"
},
{
"seq": 3,
"timestamp": 1761947179001,
"type": "step_finish"
},
{
"seq": 4,
"timestamp": 1761947180024,
"type": "step_start"
},
{
"seq": 5,
"timestamp": 1761947180306,
"type": "text",
"text": "```\n__init__.py\n__pycache__\napi.py\nconfig.py\nerrors.py\nevents.py\nhelper.py\ninvoke.py\nmcp.py\noutput.py\npermissions.py\nproc.py\nproviders.py\nrequest.py\nresponse.py\nstructured.py\nusage.py\n```"
},
{
"seq": 6,
"timestamp": 1761947180351,
"type": "step_finish"
}
],
"time": {
"start": 1761947176380,
"end": 1761947181182
},
"event_count": 6
}
```
</details>
### Synchronous Session
```python
from innerloop import Loop
loop = Loop(model="anthropic/claude-haiku-4-5")
with loop.session() as s:
s("Please remember this word for me: avocado")
response = s("What was the word I asked you to remember?")
```
```text
Output: The word you asked me to remember was **avocado**.
Duration: 4236 ms
Events: 3
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c39e98ffeZy12Mg9NGfkPEY",
"input": "What was the word I asked you to remember?",
"output": "The word you asked me to remember was **avocado**.",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947187603,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947187926,
"type": "text",
"text": "The word you asked me to remember was **avocado**."
},
{
"seq": 3,
"timestamp": 1761947187983,
"type": "step_finish"
}
],
"time": {
"start": 1761947185898,
"end": 1761947190134
},
"event_count": 3
}
```
</details>
### Asynchronous Session
```python
import asyncio
from innerloop import Loop
async def main():
loop = Loop(model="anthropic/claude-haiku-4-5")
async with loop.asession() as s:
await s("Remember this number: 42")
response = await s("What was the number?")
asyncio.run(main())
```
```text
Output: The number you asked me to remember was **42**.
Duration: 3613 ms
Events: 3
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c37b9bffemhsiMcIp7Izjls",
"input": "What was the number?",
"output": "The number you asked me to remember was **42**.",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947195220,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947195416,
"type": "text",
"text": "The number you asked me to remember was **42**."
},
{
"seq": 3,
"timestamp": 1761947195491,
"type": "step_finish"
}
],
"time": {
"start": 1761947193730,
"end": 1761947197343
},
"event_count": 3
}
```
</details>
### Structured Output
```python
from innerloop import Loop, allow
from pydantic import BaseModel
class HNStory(BaseModel):
title: str
url: str
points: int
comments: int
class HNTop(BaseModel):
stories: list[HNStory]
loop = Loop(
model="anthropic/claude-haiku-4-5",
perms=allow(webfetch=True),
)
prompt = (
"Using web search, find the current top 5 stories on Hacker News.\n"
"Prefer news.ycombinator.com (front page or item pages). For each,\n"
"return: title, url, points (int), comments (int). Output JSON with\n"
"a 'stories' array. If counts are missing, open the item page and\n"
"extract them. Keep titles unmodified.\n"
)
response = loop.run(prompt, response_format=HNTop)
```
```text
Output: Futurelock: A subtle risk in async Rust — https://rfd.shared.oxide.computer/rfd/0609
Duration: 9950 ms
Events: 7
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c35f7dffetSWcljxI1M3XaO",
"input": "Using web search, find the current top 5 stories on Hacker News.\nPrefer news.ycombinator.com (front page or item pages). For each,\nreturn: title, url, points (int), comments (int). Output JSON with\na 'stories' array. If counts are missing, open the item page and\nextract them. Keep titles unmodified.\n",
"output": {
"stories": [
{
"title": "Futurelock: A subtle risk in async Rust",
"url": "https://rfd.shared.oxide.computer/rfd/0609",
"points": 119,
"comments": 40
},
{
"title": "Introducing architecture variants",
"url": "https://discourse.ubuntu.com/t/introducing-architecture-variants-amd64v3-now-available-in-ubuntu-25-10/71312",
"points": 127,
"comments": 95
},
{
"title": "A theoretical way to circumvent Android developer verification",
"url": "https://enaix.github.io/2025/10/30/developer-verification.html",
"points": 20,
"comments": 3
},
{
"title": "Hacking India's largest automaker: Tata Motors",
"url": "https://eaton-works.com/2025/10/28/tata-motors-hack/",
"points": 75,
"comments": 24
},
{
"title": "Leaker reveals which Pixels are vulnerable to Cellebrite phone hacking",
"url": "https://arstechnica.com/gadgets/2025/10/leaker-reveals-which-pixels-are-vulnerable-to-cellebrite-phone-hacking/",
"points": 81,
"comments": 32
}
]
},
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947199808,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947199992,
"type": "text",
"text": "I'll fetch the top stories from Hacker News for you."
},
{
"seq": 3,
"timestamp": 1761947200523,
"type": "tool_use",
"output": "<html lang=\"en\" op=\"news\"><head><meta name=\"referrer\" content=\"origin\"><meta name=\"viewport\" content… (truncated)",
"status": "completed",
"tool": "webfetch"
},
{
"seq": 4,
"timestamp": 1761947200550,
"type": "step_finish"
},
{
"seq": 5,
"timestamp": 1761947201907,
"type": "step_start"
},
{
"seq": 6,
"timestamp": 1761947205636,
"type": "text",
"text": "Perfect! I have the top 5 stories from Hacker News with their points and comment counts. Let me verify the data is complete:\n\n1. Futurelock: A subtle risk in async Rust - 119 points, 40 comments\n2. Introducing architecture variants - 127 points, 95 comments\n3. A theoretical way to circumvent Android developer verification - 20 points, 3 comments\n4. Hacking India's largest automaker: Tata Motors - 75 points, 24 comments\n5. Leaker reveals which Pixels are vulnerable to Cellebrite phone hacking - 81 points, 32 comments\n\n```json\n{\n \"stories\": [\n {\n \"title\": \"Futurelock: A subtle risk in async Rust\",\n \"url\": \"https://rfd.shared.oxide.computer/rfd/0609\",\n \"points\": 119,\n \"comments\": 40\n },\n {\n \"title\": \"Introducing architecture variants\",\n \"url\": \"https://discourse.ubuntu.com/t/introducing-architecture-variants-amd64v3-now-available-in-ubuntu-25-10/71312\",\n \"points\": 127,\n \"comments\": 95\n },\n {\n \"title\": \"A theoretical way to circumvent Android developer verification\",\n \"url\": \"https://enaix.github.io/2025/10/30/developer-verification.html\",\n \"points\": 20,\n \"comments\": 3\n },\n {\n \"title\": \"Hacking India's largest automaker: Tata Motors\",\n \"url\": \"https://eaton-works.com/2025/10/28/tata-motors-hack/\",\n \"points\": 75,\n \"comments\": 24\n },\n {\n \"title\": \"Leaker reveals which Pixels are vulnerable to Cellebrite phone hacking\",\n \"url\": \"https://arstechnica.com/gadgets/2025/10/leaker-reveals-which-pixels-are-vulnerable-to-cellebrite-phone-hacking/\",\n \"points\": 81,\n \"comments\": 32\n }\n ]\n}\n```"
},
{
"seq": 7,
"timestamp": 1761947205701,
"type": "step_finish"
}
],
"time": {
"start": 1761947197346,
"end": 1761947207296
},
"event_count": 7
}
```
</details>
### Providers — LM Studio (local)
```python
from innerloop import Loop
loop = Loop(
model="lmstudio/google/gemma-3n-e4b",
providers={
"lmstudio": {
"options": {"baseURL": "http://127.0.0.1:1234/v1"}
},
},
)
response = loop.run("In one concise sentence, say something creative about coding.")
```
```text
Output: Coding is like sculpting with logic, chipping away at the unknown until a coherent form emerges.
Duration: 4947 ms
Events: 3
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c33896ffec5XSCBBCzHTROG",
"input": "In one concise sentence, say something creative about coding.",
"output": "Coding is like sculpting with logic, chipping away at the unknown until a coherent form emerges.",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947211112,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947211448,
"type": "text",
"text": "Coding is like sculpting with logic, chipping away at the unknown until a coherent form emerges."
},
{
"seq": 3,
"timestamp": 1761947211479,
"type": "step_finish"
}
],
"time": {
"start": 1761947207297,
"end": 1761947212244
},
"event_count": 3
}
```
</details>
### MCP — Remote server (Context7)
```python
from innerloop import Loop, mcp
loop = Loop(
model="anthropic/claude-sonnet-4-5",
mcp=mcp(context7="https://mcp.context7.com/mcp"),
)
prompt = (
"Use the context7 MCP server to search for FastAPI's latest "
"async database patterns. Summarize in 2-3 sentences."
)
response = loop.run(prompt)
```
```text
Output: Based on FastAPI's latest documentation, async database patterns primarily use **dependency injecti…
Duration: 15167 ms
Events: 10
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c32543ffe8L35fByCQbDmEb",
"input": "Use the context7 MCP server to search for FastAPI's latest async database patterns. Summarize in 2-3 sentences.",
"output": "Based on FastAPI's latest documentation, async database patterns primarily use **dependency injection with `yield`** to manage database sessions. You create an async function that yields a database session (e.g., SQLModel/SQLAlchemy Session), which FastAPI automatically opens before request processing and closes after the response is sent, ensuring proper resource cleanup. The pattern leverages `SessionDep = Annotated[Session, Depends(get_session)]` for cleaner type hints and uses `async with` context managers for startup/shutdown operations like creating tables on application startup.",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947214785,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947215606,
"type": "text",
"text": "I'll search for FastAPI's latest async database patterns using the Context7 MCP server."
},
{
"seq": 3,
"timestamp": 1761947216155,
"type": "tool_use",
"output": "Available Libraries (top matches):\n\nEach result includes:\n- Library ID: Context7-compatible identifi… (truncated)",
"status": "completed",
"tool": "context7_resolve_library_id"
},
{
"seq": 4,
"timestamp": 1761947216186,
"type": "step_finish"
},
{
"seq": 5,
"timestamp": 1761947218217,
"type": "step_start"
},
{
"seq": 6,
"timestamp": 1761947219639,
"type": "tool_use",
"output": "### FastAPI Dependency with `yield` and `async with` (Python)\n\nSource: https://github.com/fastapi/fa… (truncated)",
"status": "completed",
"tool": "context7_get_library_docs"
},
{
"seq": 7,
"timestamp": 1761947219665,
"type": "step_finish"
},
{
"seq": 8,
"timestamp": 1761947222237,
"type": "step_start"
},
{
"seq": 9,
"timestamp": 1761947225651,
"type": "text",
"text": "Based on FastAPI's latest documentation, async database patterns primarily use **dependency injection with `yield`** to manage database sessions. You create an async function that yields a database session (e.g., SQLModel/SQLAlchemy Session), which FastAPI automatically opens before request processing and closes after the response is sent, ensuring proper resource cleanup. The pattern leverages `SessionDep = Annotated[Session, Depends(get_session)]` for cleaner type hints and uses `async with` context managers for startup/shutdown operations like creating tables on application startup."
},
{
"seq": 10,
"timestamp": 1761947225704,
"type": "step_finish"
}
],
"time": {
"start": 1761947212244,
"end": 1761947227411
},
"event_count": 10
}
```
</details>
### MCP — Local server (BioMCP)
```python
from innerloop import Loop, mcp
loop = Loop(
model="anthropic/claude-sonnet-4-5",
mcp=mcp(biomcp="uvx --from biomcp-python biomcp run"),
)
prompt = (
"Using ONLY the biomcp MCP server tools, look up the BRAF V600E mutation. "
"List associated cancer types and targeted drug therapies in 2-3 sentences."
)
response = loop.run(prompt, timeout=60.0)
```
```text
Output: Based on the biomcp search results, **BRAF V600E mutation is associated with multiple cancer types …
Duration: 40244 ms
Events: 17
```
<details>
<summary>JSON Output</summary>
```json
{
"session_id": "ses_5c3c2ea04ffexW3ZKYVCf4tuJI",
"input": "Using ONLY the biomcp MCP server tools, look up the BRAF V600E mutation. List associated cancer types and targeted drug therapies in 2-3 sentences.",
"output": "Based on the biomcp search results, **BRAF V600E mutation is associated with multiple cancer types including melanoma, thyroid cancer, colorectal cancer, and high-grade glioma**. **Targeted therapies include BRAF inhibitors (dabrafenib, vemurafenib) often combined with MEK inhibitors (trametinib), which specifically target the mutated BRAF protein and downstream MAPK pathway signaling**. The literature shows these combination therapies are used across different BRAF V600E-positive malignancies, though resistance mechanisms remain a clinical challenge.",
"attempts": 1,
"events": [
{
"seq": 1,
"timestamp": 1761947230522,
"type": "step_start"
},
{
"seq": 2,
"timestamp": 1761947231190,
"type": "text",
"text": "I'll look up the BRAF V600E mutation using the biomcp tools."
},
{
"seq": 3,
"timestamp": 1761947234217,
"type": "tool_use",
"output": "{\n \"domain\": \"thinking\",\n \"result\": \"Added thought 1 to main sequence. Progress: 1/3 thoughts. Nex… (truncated)",
"status": "completed",
"tool": "biomcp_think"
},
{
"seq": 4,
"timestamp": 1761947234312,
"type": "step_finish"
},
{
"seq": 5,
"timestamp": 1761947236128,
"type": "step_start"
},
{
"seq": 6,
"timestamp": 1761947239225,
"type": "tool_use",
"output": "{\n \"domain\": \"thinking\",\n \"result\": \"Added thought 2 to main sequence. Progress: 2/3 thoughts. Nex… (truncated)",
"status": "completed",
"tool": "biomcp_think"
},
{
"seq": 7,
"timestamp": 1761947239254,
"type": "step_finish"
},
{
"seq": 8,
"timestamp": 1761947241093,
"type": "step_start"
},
{
"seq": 9,
"timestamp": 1761947255261,
"type": "tool_use",
"output": "# Record 1\nId: chr10:g.32800350C>A\nScore: 1.0\nChrom: 10\n\n## Cadd\nLicense: http://bit.ly/2TIuab9\nPhre… (truncated)",
"status": "completed",
"tool": "biomcp_variant_searcher"
},
{
"seq": 10,
"timestamp": 1761947255927,
"type": "tool_use",
"output": "\n### cBioPortal Summary for BRAF\n- **Mutation Frequency**: 0.0% (0 mutations in 0 samples)\n- **Studi… (truncated)",
"status": "completed",
"tool": "biomcp_article_searcher"
},
{
"seq": 11,
"timestamp": 1761947255957,
"type": "step_finish"
},
{
"seq": 12,
"timestamp": 1761947257893,
"type": "step_start"
},
{
"seq": 13,
"timestamp": 1761947261508,
"type": "tool_use",
"output": "{\n \"domain\": \"thinking\",\n \"result\": \"Added thought 3 to main sequence. Progress: 3/3 thoughts. Thi… (truncated)",
"status": "completed",
"tool": "biomcp_think"
},
{
"seq": 14,
"timestamp": 1761947261558,
"type": "step_finish"
},
{
"seq": 15,
"timestamp": 1761947263073,
"type": "step_start"
},
{
"seq": 16,
"timestamp": 1761947266114,
"type": "text",
"text": "Based on the biomcp search results, **BRAF V600E mutation is associated with multiple cancer types including melanoma, thyroid cancer, colorectal cancer, and high-grade glioma**. **Targeted therapies include BRAF inhibitors (dabrafenib, vemurafenib) often combined with MEK inhibitors (trametinib), which specifically target the mutated BRAF protein and downstream MAPK pathway signaling**. The literature shows these combination therapies are used across different BRAF V600E-positive malignancies, though resistance mechanisms remain a clinical challenge."
},
{
"seq": 17,
"timestamp": 1761947266162,
"type": "step_finish"
}
],
"time": {
"start": 1761947227411,
"end": 1761947267655
},
"event_count": 17
}
```
</details>
<!-- END USAGE -->
Raw data
{
"_id": null,
"home_page": null,
"name": "innerloop",
"maintainer": null,
"docs_url": null,
"requires_python": ">=3.10",
"maintainer_email": null,
"keywords": "agent, ai, anthropic, automation, claude, codex, devtool, gemini, llm, openai, sdk",
"author": null,
"author_email": "Ian Maurer <imaurer@gmail.com>",
"download_url": "https://files.pythonhosted.org/packages/b7/db/5d185481609691c0d749dd2ec6f1f135a2577469e232aa4b1ef25c5f9e38/innerloop-0.0.0.dev2.tar.gz",
"platform": null,
"description": "# InnerLoop\n\nPython SDK for invoking the [OpenCode](https://opencode.ai/) coding CLI in a headless, non-interactive manner.\n\nSupports:\n\n- Synchronous and Asynchronous modes\n- Structured outputs using Pydantic models\n- Sessions for multiple invocations with shared context\n- Permission configuration for reading, writing, web, and bash tools\n- Configurable working directory per loop, session, or call\n\n## Quick Start\n\nSee [docs/guides/installing-opencode.md](docs/guides/installing-opencode.md) for installation and configuration.\n\nQuick install:\n```bash\nmake install_opencode # Handles bun config automatically\n```\n\nOr manually:\n```bash\nnpm install -g opencode-ai\nexport OPENCODE_API_KEY=your-key\nopencode models\n```\n\n## Installation\n\nYou can install `innerloop` using `uv` (recommended) or `pip`.\n\n```bash\n# Using uv\nuv pip install innerloop\n\n# Using pip\npip install innerloop\n```\n\n## Prerequisites\n\n**InnerLoop requires the OpenCode CLI.** Install it and ensure `opencode` is on your PATH:\n\n```bash\nopencode --version\n```\n\nIf the command fails, install OpenCode from [opencode.ai](https://opencode.ai/) and add it to your PATH.\n\nSee the [OpenCode documentation](https://opencode.ai/docs/) for installation instructions.\n\n## Usage\n\n<!-- BEGIN USAGE -->\nWe summarize results below each snippet without `print()` in the examples.\nSummary shows: Output (first 100 chars), Duration (ms), Events.\n\nTo render yourself from a Response object:\n\n```python\ndef show(resp):\n print('Output:', str(resp.output)[:100])\n dur = (resp.time.end - resp.time.start) if resp.time else 0\n print(f'Duration: {dur} ms')\n print(f'Events: {resp.event_count}')\n print(resp.model_dump_json(by_alias=True, indent=4))\n```\n\nSee: src/innerloop/response.py\n\n\n### Synchronous Run\n\n```python\nfrom innerloop import Loop\n\nloop = Loop(model=\"anthropic/claude-haiku-4-5\")\nresponse = loop.run(\"Say hello, one short line.\")\n```\n\n```text\nOutput: Hello! How can I help you with your code today?\nDuration: 3203 ms\nEvents: 3\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c3ddbaffe1xdYfKWI7bvq3I\",\n \"input\": \"Say hello, one short line.\",\n \"output\": \"Hello! How can I help you with your code today?\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947166821,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947166943,\n \"type\": \"text\",\n \"text\": \"Hello! How can I help you with your code today?\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947166991,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947165017,\n \"end\": 1761947168220\n },\n \"event_count\": 3\n}\n```\n</details>\n\n### Asynchronous Run\n\n```python\nimport asyncio\nfrom innerloop import Loop\n\nasync def main():\n loop = Loop(model=\"anthropic/claude-haiku-4-5\")\n async with loop.asession() as s:\n await s(\"Remember this number: 42\")\n response = await s(\"What was the number?\")\n\nasyncio.run(main())\n```\n\n```text\nOutput: The number was 42.\nDuration: 4165 ms\nEvents: 3\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c3d139ffeguE8rhOWW70ST2\",\n \"input\": \"What was the number?\",\n \"output\": \"The number was 42.\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947173793,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947173971,\n \"type\": \"text\",\n \"text\": \"The number was 42.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947174039,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947172214,\n \"end\": 1761947176379\n },\n \"event_count\": 3\n}\n```\n</details>\n\n### Tool Use (with workdir)\n\n```python\nfrom innerloop import Loop, allow\n\nloop = Loop(\n model=\"anthropic/claude-haiku-4-5\",\n perms=allow(\"bash\"),\n)\nloop.default_workdir = \"src/innerloop\"\nresponse = loop.run(\"Use bash: ls -1\\nReturn only the raw command output.\")\n```\n\n```text\nOutput: ```\n__init__.py\n__pycache__\napi.py\nconfig.py\nerrors.py\nevents.py\nhelper.py\ninvoke.py\nmcp.py\noutput.\u2026\nDuration: 4802 ms\nEvents: 6\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c3b15cffecPu1g9yj32ee7j\",\n \"input\": \"Use bash: ls -1\\nReturn only the raw command output.\",\n \"output\": \"```\\n__init__.py\\n__pycache__\\napi.py\\nconfig.py\\nerrors.py\\nevents.py\\nhelper.py\\ninvoke.py\\nmcp.py\\noutput.py\\npermissions.py\\nproc.py\\nproviders.py\\nrequest.py\\nresponse.py\\nstructured.py\\nusage.py\\n```\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947178427,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947178973,\n \"type\": \"tool_use\",\n \"output\": \"__init__.py\\n__pycache__\\napi.py\\nconfig.py\\nerrors.py\\nevents.py\\nhelper.py\\ninvoke.py\\nmcp.py\\noutput.py\\npe\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"bash\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947179001,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 4,\n \"timestamp\": 1761947180024,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 5,\n \"timestamp\": 1761947180306,\n \"type\": \"text\",\n \"text\": \"```\\n__init__.py\\n__pycache__\\napi.py\\nconfig.py\\nerrors.py\\nevents.py\\nhelper.py\\ninvoke.py\\nmcp.py\\noutput.py\\npermissions.py\\nproc.py\\nproviders.py\\nrequest.py\\nresponse.py\\nstructured.py\\nusage.py\\n```\"\n },\n {\n \"seq\": 6,\n \"timestamp\": 1761947180351,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947176380,\n \"end\": 1761947181182\n },\n \"event_count\": 6\n}\n```\n</details>\n\n### Synchronous Session\n\n```python\nfrom innerloop import Loop\n\nloop = Loop(model=\"anthropic/claude-haiku-4-5\")\nwith loop.session() as s:\n s(\"Please remember this word for me: avocado\")\n response = s(\"What was the word I asked you to remember?\")\n```\n\n```text\nOutput: The word you asked me to remember was **avocado**.\nDuration: 4236 ms\nEvents: 3\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c39e98ffeZy12Mg9NGfkPEY\",\n \"input\": \"What was the word I asked you to remember?\",\n \"output\": \"The word you asked me to remember was **avocado**.\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947187603,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947187926,\n \"type\": \"text\",\n \"text\": \"The word you asked me to remember was **avocado**.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947187983,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947185898,\n \"end\": 1761947190134\n },\n \"event_count\": 3\n}\n```\n</details>\n\n### Asynchronous Session\n\n```python\nimport asyncio\nfrom innerloop import Loop\n\nasync def main():\n loop = Loop(model=\"anthropic/claude-haiku-4-5\")\n async with loop.asession() as s:\n await s(\"Remember this number: 42\")\n response = await s(\"What was the number?\")\n\nasyncio.run(main())\n```\n\n```text\nOutput: The number you asked me to remember was **42**.\nDuration: 3613 ms\nEvents: 3\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c37b9bffemhsiMcIp7Izjls\",\n \"input\": \"What was the number?\",\n \"output\": \"The number you asked me to remember was **42**.\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947195220,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947195416,\n \"type\": \"text\",\n \"text\": \"The number you asked me to remember was **42**.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947195491,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947193730,\n \"end\": 1761947197343\n },\n \"event_count\": 3\n}\n```\n</details>\n\n### Structured Output\n\n```python\nfrom innerloop import Loop, allow\nfrom pydantic import BaseModel\n\nclass HNStory(BaseModel):\n title: str\n url: str\n points: int\n comments: int\n\nclass HNTop(BaseModel):\n stories: list[HNStory]\n\nloop = Loop(\n model=\"anthropic/claude-haiku-4-5\",\n perms=allow(webfetch=True),\n)\n\nprompt = (\n \"Using web search, find the current top 5 stories on Hacker News.\\n\"\n \"Prefer news.ycombinator.com (front page or item pages). For each,\\n\"\n \"return: title, url, points (int), comments (int). Output JSON with\\n\"\n \"a 'stories' array. If counts are missing, open the item page and\\n\"\n \"extract them. Keep titles unmodified.\\n\"\n)\nresponse = loop.run(prompt, response_format=HNTop)\n```\n\n```text\nOutput: Futurelock: A subtle risk in async Rust \u2014 https://rfd.shared.oxide.computer/rfd/0609\nDuration: 9950 ms\nEvents: 7\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c35f7dffetSWcljxI1M3XaO\",\n \"input\": \"Using web search, find the current top 5 stories on Hacker News.\\nPrefer news.ycombinator.com (front page or item pages). For each,\\nreturn: title, url, points (int), comments (int). Output JSON with\\na 'stories' array. If counts are missing, open the item page and\\nextract them. Keep titles unmodified.\\n\",\n \"output\": {\n \"stories\": [\n {\n \"title\": \"Futurelock: A subtle risk in async Rust\",\n \"url\": \"https://rfd.shared.oxide.computer/rfd/0609\",\n \"points\": 119,\n \"comments\": 40\n },\n {\n \"title\": \"Introducing architecture variants\",\n \"url\": \"https://discourse.ubuntu.com/t/introducing-architecture-variants-amd64v3-now-available-in-ubuntu-25-10/71312\",\n \"points\": 127,\n \"comments\": 95\n },\n {\n \"title\": \"A theoretical way to circumvent Android developer verification\",\n \"url\": \"https://enaix.github.io/2025/10/30/developer-verification.html\",\n \"points\": 20,\n \"comments\": 3\n },\n {\n \"title\": \"Hacking India's largest automaker: Tata Motors\",\n \"url\": \"https://eaton-works.com/2025/10/28/tata-motors-hack/\",\n \"points\": 75,\n \"comments\": 24\n },\n {\n \"title\": \"Leaker reveals which Pixels are vulnerable to Cellebrite phone hacking\",\n \"url\": \"https://arstechnica.com/gadgets/2025/10/leaker-reveals-which-pixels-are-vulnerable-to-cellebrite-phone-hacking/\",\n \"points\": 81,\n \"comments\": 32\n }\n ]\n },\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947199808,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947199992,\n \"type\": \"text\",\n \"text\": \"I'll fetch the top stories from Hacker News for you.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947200523,\n \"type\": \"tool_use\",\n \"output\": \"<html lang=\\\"en\\\" op=\\\"news\\\"><head><meta name=\\\"referrer\\\" content=\\\"origin\\\"><meta name=\\\"viewport\\\" content\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"webfetch\"\n },\n {\n \"seq\": 4,\n \"timestamp\": 1761947200550,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 5,\n \"timestamp\": 1761947201907,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 6,\n \"timestamp\": 1761947205636,\n \"type\": \"text\",\n \"text\": \"Perfect! I have the top 5 stories from Hacker News with their points and comment counts. Let me verify the data is complete:\\n\\n1. Futurelock: A subtle risk in async Rust - 119 points, 40 comments\\n2. Introducing architecture variants - 127 points, 95 comments\\n3. A theoretical way to circumvent Android developer verification - 20 points, 3 comments\\n4. Hacking India's largest automaker: Tata Motors - 75 points, 24 comments\\n5. Leaker reveals which Pixels are vulnerable to Cellebrite phone hacking - 81 points, 32 comments\\n\\n```json\\n{\\n \\\"stories\\\": [\\n {\\n \\\"title\\\": \\\"Futurelock: A subtle risk in async Rust\\\",\\n \\\"url\\\": \\\"https://rfd.shared.oxide.computer/rfd/0609\\\",\\n \\\"points\\\": 119,\\n \\\"comments\\\": 40\\n },\\n {\\n \\\"title\\\": \\\"Introducing architecture variants\\\",\\n \\\"url\\\": \\\"https://discourse.ubuntu.com/t/introducing-architecture-variants-amd64v3-now-available-in-ubuntu-25-10/71312\\\",\\n \\\"points\\\": 127,\\n \\\"comments\\\": 95\\n },\\n {\\n \\\"title\\\": \\\"A theoretical way to circumvent Android developer verification\\\",\\n \\\"url\\\": \\\"https://enaix.github.io/2025/10/30/developer-verification.html\\\",\\n \\\"points\\\": 20,\\n \\\"comments\\\": 3\\n },\\n {\\n \\\"title\\\": \\\"Hacking India's largest automaker: Tata Motors\\\",\\n \\\"url\\\": \\\"https://eaton-works.com/2025/10/28/tata-motors-hack/\\\",\\n \\\"points\\\": 75,\\n \\\"comments\\\": 24\\n },\\n {\\n \\\"title\\\": \\\"Leaker reveals which Pixels are vulnerable to Cellebrite phone hacking\\\",\\n \\\"url\\\": \\\"https://arstechnica.com/gadgets/2025/10/leaker-reveals-which-pixels-are-vulnerable-to-cellebrite-phone-hacking/\\\",\\n \\\"points\\\": 81,\\n \\\"comments\\\": 32\\n }\\n ]\\n}\\n```\"\n },\n {\n \"seq\": 7,\n \"timestamp\": 1761947205701,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947197346,\n \"end\": 1761947207296\n },\n \"event_count\": 7\n}\n```\n</details>\n\n### Providers \u2014 LM Studio (local)\n\n```python\nfrom innerloop import Loop\n\nloop = Loop(\n model=\"lmstudio/google/gemma-3n-e4b\",\n providers={\n \"lmstudio\": {\n \"options\": {\"baseURL\": \"http://127.0.0.1:1234/v1\"}\n },\n },\n)\n\nresponse = loop.run(\"In one concise sentence, say something creative about coding.\")\n```\n\n```text\nOutput: Coding is like sculpting with logic, chipping away at the unknown until a coherent form emerges.\nDuration: 4947 ms\nEvents: 3\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c33896ffec5XSCBBCzHTROG\",\n \"input\": \"In one concise sentence, say something creative about coding.\",\n \"output\": \"Coding is like sculpting with logic, chipping away at the unknown until a coherent form emerges.\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947211112,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947211448,\n \"type\": \"text\",\n \"text\": \"Coding is like sculpting with logic, chipping away at the unknown until a coherent form emerges.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947211479,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947207297,\n \"end\": 1761947212244\n },\n \"event_count\": 3\n}\n```\n</details>\n\n### MCP \u2014 Remote server (Context7)\n\n```python\nfrom innerloop import Loop, mcp\n\nloop = Loop(\n model=\"anthropic/claude-sonnet-4-5\",\n mcp=mcp(context7=\"https://mcp.context7.com/mcp\"),\n)\nprompt = (\n \"Use the context7 MCP server to search for FastAPI's latest \"\n \"async database patterns. Summarize in 2-3 sentences.\"\n)\nresponse = loop.run(prompt)\n```\n\n```text\nOutput: Based on FastAPI's latest documentation, async database patterns primarily use **dependency injecti\u2026\nDuration: 15167 ms\nEvents: 10\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c32543ffe8L35fByCQbDmEb\",\n \"input\": \"Use the context7 MCP server to search for FastAPI's latest async database patterns. Summarize in 2-3 sentences.\",\n \"output\": \"Based on FastAPI's latest documentation, async database patterns primarily use **dependency injection with `yield`** to manage database sessions. You create an async function that yields a database session (e.g., SQLModel/SQLAlchemy Session), which FastAPI automatically opens before request processing and closes after the response is sent, ensuring proper resource cleanup. The pattern leverages `SessionDep = Annotated[Session, Depends(get_session)]` for cleaner type hints and uses `async with` context managers for startup/shutdown operations like creating tables on application startup.\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947214785,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947215606,\n \"type\": \"text\",\n \"text\": \"I'll search for FastAPI's latest async database patterns using the Context7 MCP server.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947216155,\n \"type\": \"tool_use\",\n \"output\": \"Available Libraries (top matches):\\n\\nEach result includes:\\n- Library ID: Context7-compatible identifi\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"context7_resolve_library_id\"\n },\n {\n \"seq\": 4,\n \"timestamp\": 1761947216186,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 5,\n \"timestamp\": 1761947218217,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 6,\n \"timestamp\": 1761947219639,\n \"type\": \"tool_use\",\n \"output\": \"### FastAPI Dependency with `yield` and `async with` (Python)\\n\\nSource: https://github.com/fastapi/fa\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"context7_get_library_docs\"\n },\n {\n \"seq\": 7,\n \"timestamp\": 1761947219665,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 8,\n \"timestamp\": 1761947222237,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 9,\n \"timestamp\": 1761947225651,\n \"type\": \"text\",\n \"text\": \"Based on FastAPI's latest documentation, async database patterns primarily use **dependency injection with `yield`** to manage database sessions. You create an async function that yields a database session (e.g., SQLModel/SQLAlchemy Session), which FastAPI automatically opens before request processing and closes after the response is sent, ensuring proper resource cleanup. The pattern leverages `SessionDep = Annotated[Session, Depends(get_session)]` for cleaner type hints and uses `async with` context managers for startup/shutdown operations like creating tables on application startup.\"\n },\n {\n \"seq\": 10,\n \"timestamp\": 1761947225704,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947212244,\n \"end\": 1761947227411\n },\n \"event_count\": 10\n}\n```\n</details>\n\n### MCP \u2014 Local server (BioMCP)\n\n```python\nfrom innerloop import Loop, mcp\n\nloop = Loop(\n model=\"anthropic/claude-sonnet-4-5\",\n mcp=mcp(biomcp=\"uvx --from biomcp-python biomcp run\"),\n)\nprompt = (\n \"Using ONLY the biomcp MCP server tools, look up the BRAF V600E mutation. \"\n \"List associated cancer types and targeted drug therapies in 2-3 sentences.\"\n)\nresponse = loop.run(prompt, timeout=60.0)\n```\n\n```text\nOutput: Based on the biomcp search results, **BRAF V600E mutation is associated with multiple cancer types \u2026\nDuration: 40244 ms\nEvents: 17\n```\n\n<details>\n <summary>JSON Output</summary>\n\n```json\n{\n \"session_id\": \"ses_5c3c2ea04ffexW3ZKYVCf4tuJI\",\n \"input\": \"Using ONLY the biomcp MCP server tools, look up the BRAF V600E mutation. List associated cancer types and targeted drug therapies in 2-3 sentences.\",\n \"output\": \"Based on the biomcp search results, **BRAF V600E mutation is associated with multiple cancer types including melanoma, thyroid cancer, colorectal cancer, and high-grade glioma**. **Targeted therapies include BRAF inhibitors (dabrafenib, vemurafenib) often combined with MEK inhibitors (trametinib), which specifically target the mutated BRAF protein and downstream MAPK pathway signaling**. The literature shows these combination therapies are used across different BRAF V600E-positive malignancies, though resistance mechanisms remain a clinical challenge.\",\n \"attempts\": 1,\n \"events\": [\n {\n \"seq\": 1,\n \"timestamp\": 1761947230522,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 2,\n \"timestamp\": 1761947231190,\n \"type\": \"text\",\n \"text\": \"I'll look up the BRAF V600E mutation using the biomcp tools.\"\n },\n {\n \"seq\": 3,\n \"timestamp\": 1761947234217,\n \"type\": \"tool_use\",\n \"output\": \"{\\n \\\"domain\\\": \\\"thinking\\\",\\n \\\"result\\\": \\\"Added thought 1 to main sequence. Progress: 1/3 thoughts. Nex\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"biomcp_think\"\n },\n {\n \"seq\": 4,\n \"timestamp\": 1761947234312,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 5,\n \"timestamp\": 1761947236128,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 6,\n \"timestamp\": 1761947239225,\n \"type\": \"tool_use\",\n \"output\": \"{\\n \\\"domain\\\": \\\"thinking\\\",\\n \\\"result\\\": \\\"Added thought 2 to main sequence. Progress: 2/3 thoughts. Nex\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"biomcp_think\"\n },\n {\n \"seq\": 7,\n \"timestamp\": 1761947239254,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 8,\n \"timestamp\": 1761947241093,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 9,\n \"timestamp\": 1761947255261,\n \"type\": \"tool_use\",\n \"output\": \"# Record 1\\nId: chr10:g.32800350C>A\\nScore: 1.0\\nChrom: 10\\n\\n## Cadd\\nLicense: http://bit.ly/2TIuab9\\nPhre\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"biomcp_variant_searcher\"\n },\n {\n \"seq\": 10,\n \"timestamp\": 1761947255927,\n \"type\": \"tool_use\",\n \"output\": \"\\n### cBioPortal Summary for BRAF\\n- **Mutation Frequency**: 0.0% (0 mutations in 0 samples)\\n- **Studi\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"biomcp_article_searcher\"\n },\n {\n \"seq\": 11,\n \"timestamp\": 1761947255957,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 12,\n \"timestamp\": 1761947257893,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 13,\n \"timestamp\": 1761947261508,\n \"type\": \"tool_use\",\n \"output\": \"{\\n \\\"domain\\\": \\\"thinking\\\",\\n \\\"result\\\": \\\"Added thought 3 to main sequence. Progress: 3/3 thoughts. Thi\u2026 (truncated)\",\n \"status\": \"completed\",\n \"tool\": \"biomcp_think\"\n },\n {\n \"seq\": 14,\n \"timestamp\": 1761947261558,\n \"type\": \"step_finish\"\n },\n {\n \"seq\": 15,\n \"timestamp\": 1761947263073,\n \"type\": \"step_start\"\n },\n {\n \"seq\": 16,\n \"timestamp\": 1761947266114,\n \"type\": \"text\",\n \"text\": \"Based on the biomcp search results, **BRAF V600E mutation is associated with multiple cancer types including melanoma, thyroid cancer, colorectal cancer, and high-grade glioma**. **Targeted therapies include BRAF inhibitors (dabrafenib, vemurafenib) often combined with MEK inhibitors (trametinib), which specifically target the mutated BRAF protein and downstream MAPK pathway signaling**. The literature shows these combination therapies are used across different BRAF V600E-positive malignancies, though resistance mechanisms remain a clinical challenge.\"\n },\n {\n \"seq\": 17,\n \"timestamp\": 1761947266162,\n \"type\": \"step_finish\"\n }\n ],\n \"time\": {\n \"start\": 1761947227411,\n \"end\": 1761947267655\n },\n \"event_count\": 17\n}\n```\n</details>\n<!-- END USAGE -->\n",
"bugtrack_url": null,
"license": null,
"summary": "LLM in a loop with tools, MCP, sessions, and structured outputs.",
"version": "0.0.0.dev2",
"project_urls": {
"Changelog": "https://botassembly.org/innerloop/changelog/",
"Documentation": "https://botassembly.org/innerloop",
"Homepage": "https://github.com/botassembly/innerloop",
"Issues": "https://github.com/botassembly/innerloop/issues",
"Repository": "https://github.com/botassembly/innerloop"
},
"split_keywords": [
"agent",
" ai",
" anthropic",
" automation",
" claude",
" codex",
" devtool",
" gemini",
" llm",
" openai",
" sdk"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "e7bbdbade7331552cdbb215d776c2d3119b8c26b7fdf5efd2080f66bc58e2142",
"md5": "3de98c04360d4c3859e7651c340260a4",
"sha256": "05821d8dda5ceeacf4a61f7d527a71b170f2a75b5c8c25d756c0484fa16b811b"
},
"downloads": -1,
"filename": "innerloop-0.0.0.dev2-py3-none-any.whl",
"has_sig": false,
"md5_digest": "3de98c04360d4c3859e7651c340260a4",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.10",
"size": 29597,
"upload_time": "2025-11-02T17:50:47",
"upload_time_iso_8601": "2025-11-02T17:50:47.132429Z",
"url": "https://files.pythonhosted.org/packages/e7/bb/dbade7331552cdbb215d776c2d3119b8c26b7fdf5efd2080f66bc58e2142/innerloop-0.0.0.dev2-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "b7db5d185481609691c0d749dd2ec6f1f135a2577469e232aa4b1ef25c5f9e38",
"md5": "e211cc8e843164c32c81e629f0c44c68",
"sha256": "94a68f0b524f86fab25e5467b1d87c88e988b71fdb58adae61086fcdf31174ec"
},
"downloads": -1,
"filename": "innerloop-0.0.0.dev2.tar.gz",
"has_sig": false,
"md5_digest": "e211cc8e843164c32c81e629f0c44c68",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.10",
"size": 27790,
"upload_time": "2025-11-02T17:50:48",
"upload_time_iso_8601": "2025-11-02T17:50:48.722247Z",
"url": "https://files.pythonhosted.org/packages/b7/db/5d185481609691c0d749dd2ec6f1f135a2577469e232aa4b1ef25c5f9e38/innerloop-0.0.0.dev2.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-11-02 17:50:48",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "botassembly",
"github_project": "innerloop",
"github_not_found": true,
"lcname": "innerloop"
}