Files
agno/cookbook/05_agent_os/27_filesystem/basic.py
T
3be277189d feat: continue ClaudeAgent runs from a step (#10917)
## Summary

Adds step replay to `ClaudeAgent` using the same API as native
`Agent.continue_run`. Stacked on #10905
(`feat/harness-agents-phase2-astra`); it needs #10904's database-backed
Claude SDK transcript store.

- **`ClaudeAgent.continue_run` / `acontinue_run`**: accept
`continue_from` (`"end"`, `"last_user"` or a message index), `fork` and
`input`, and support `stream=True` and `background=True`. Continuing
forks the stored SDK transcript at the chosen message
(`fork_session_via_store(..., up_to_message_id=...)`) and resumes the
fork, so the model only remembers what happened up to that point.
- `fork=True` writes a sibling run with `forked_from_run_id` /
`forked_from_message_index`; `fork=False` replaces the source run in
place and keeps its fork lineage.
- `continue_from="last_user"` without `input` replays the whole turn: it
forks just before the prompt (or starts a fresh SDK session for the
session's first turn) and re-sends the original input.
- A mid-turn continue without `input` sends "Continue from where you
left off."
- Later turns in the session continue from the replayed branch, because
the session's SDK pointer moves to the fork.
- **Transcript positions on runs**: the adapter enables the SDK's
`replay-user-messages` so the prompt arrives with its transcript uuid.
It records the SDK session id and uuid for each top-level prompt, tool
call, tool result and final reply in
`Message.provider_data["claude_sdk"]`. Tool results are marked as
checkpoints, so `GET /agents/{id}/runs/{run_id}/checkpoints` lists each
step.
- **AgentOS**: no router changes. `/continue` and `/checkpoints` already
dispatch on `acontinue_run` / `aget_run_output`, so `ClaudeAgent` moves
from 501 to supported. Unsupported options (`regenerate`,
`replace_original`, `additional_instructions`, HITL `tools`) return 400.
- **`BaseExternalAgent`**: generic plumbing only. `ExternalContinuation`
carries the kept transcript into `_arun_non_stream` / `_arun_stream`, a
per-run `run_state` dict reaches the adapter, and an
`_annotate_run_output` hook (no-op by default) lets an adapter attach
per-message data. Other adapters are unaffected: none forwards run
kwargs to its framework.
- **Resume fallback unchanged for normal runs**: the echoed prompt is
not counted as a received message, so #10903's "stored session cannot be
resumed, start fresh with history" fallback still works. Continuations
never fall back; a failed fork resume errors instead of silently
replaying the full history.

## Type of change

- [ ] Bug fix
- [x] New feature
- [ ] Breaking change
- [ ] Improvement
- [ ] Model update
- [ ] Other:

---

## Checklist

- [x] Code complies with style guidelines
- [x] Ran format/validation scripts (`./scripts/format.sh` and
`./scripts/validate.sh`)
- [x] Self-review completed
- [x] Documentation updated (comments, docstrings)
- [x] Examples and guides: Relevant cookbook examples have been included
or updated (if applicable)
- [x] Tested in clean environment
- [x] Tests added/updated (if applicable)

### Duplicate and AI-Generated PR Check

- [x] I have searched existing [open pull
requests](https://github.com/agno-agi/agno/pulls) and confirmed that no
other PR already addresses this issue
- [ ] If a similar PR exists, I have explained below why this PR is a
better approach
- [x] Check if this PR was entirely AI-generated (by Copilot, Claude
Code, Cursor, etc.)

---

## Additional Notes

Base is `feat/harness-agents-phase2-astra` (#10905), not `main`. Merge
after #10903, #10904 and #10905.

Limits:
- Only runs recorded with transcript storage (PostgresDb,
AsyncPostgresDb, SqliteDb, AsyncSqliteDb) can be continued; older runs
and other databases raise a clear error.
- Files and other side effects after the checkpoint are not rewound. The
SDK's file checkpointing does not carry over to forked sessions.
- Steps inside subagents (`parent_tool_use_id`) are not checkpoints;
they live in subagent transcripts.
- `regenerate` / `replace_original` and Codex are follow-ups.

Verification:
- Confirmed against the real SDK (0.2.95) that streamed message uuids
equal stored transcript uuids, and that forks cut at a tool result or
before the prompt only remember up to that point.
- Live end-to-end on SQLite: fork from a tool result, replay of a first
and a later turn, in-place streamed continue, follow-up turn on the
branch, and AgentOS `/checkpoints` + `/continue` (non-stream and SSE).
`cookbook/frameworks/claude-agent-sdk/continue_from.py` passes with
`claude-sonnet-4-6`.
- New unit tests against the fake SDK: transcript positions and
checkpoints (stream and non-stream), fork from a tool result, in-place
continue lineage (sync API), turn replay with and without a parent
entry, no fallback on a failed continuation, rejected options, and
prompt echo not blocking the resume fallback. Each guard was
mutation-checked: breaking lineage, allowing fallback, or counting the
echo as received makes a test fail.
- `validate.sh`: no ruff or mypy findings in touched files; the
remaining mypy errors are pre-existing model/MCP SDK typing issues from
local dependency versions. Postgres integration tests were not run
locally.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Ashpreet <[email protected]>
Co-authored-by: Ashpreet <[email protected]>
Co-authored-by: kausmeows <[email protected]>
Co-authored-by: Claude Fable 5.1 <[email protected]>
2026-10-09 21:39:26 -04:00

47 lines
1.2 KiB
Python

"""
AgentOS File System
===================
Enable a private durable filesystem with ``filesystem=True``. By default files
are scoped to the agent. AgentOS adds user scope only when user isolation is on.
Prerequisites: OPENAI_API_KEY is needed only for agent runs
Run: .venvs/demo/bin/python cookbook/05_agent_os/27_filesystem/basic.py
Try: Connect Agno OS to http://localhost:7777 and open File System
"""
from os import getenv
from agno.agent import Agent
from agno.db.postgres import PostgresDb
from agno.models.openai import OpenAIResponses
from agno.os import AgentOS
db = PostgresDb(
id="agentos-knowledge-postgres",
db_url=getenv("DATABASE_URL", "postgresql+psycopg://ai:ai@localhost:5532/ai"),
)
filesystem_agent = Agent(
id="filesystem-agent",
name="File System Agent",
model=OpenAIResponses(id="gpt-5.6-luna"),
db=db,
filesystem=True,
instructions="Keep durable working notes in your filesystem.",
markdown=True,
debug_mode=True,
)
agent_os = AgentOS(
id="filesystem-os",
description="AgentOS with a durable per-agent filesystem.",
db=db,
agents=[filesystem_agent],
)
app = agent_os.get_app()
if __name__ == "__main__":
agent_os.serve(app="basic:app", reload=True)