async-io-model — скилл для Claude Code: что делает и как установить
Обучает агента архитектуре асинхронного ввода-вывода в Turso: явным стейт-машинам, типу IOResult и защите от багов повторного входа.
npx skills add tursodatabase/turso --skill async-io-model -a claude-codeЧто делает async-io-model
В ядре Turso разработчики отказались от Rust async/await в пользу кооперативной многозадачности на явных стейт-машинах. Скилл передаёт агенту правила работы с типом IOResult, структурами Completion и CompletionGroup, а также макросами return_if_io! и io_yield_one!. ИИ начинает строить функции, возвращающие Done или IO, как это принято в репозитории проекта.
Главная проблема этой схемы — баги повторного входа. Если метод увеличивает счётчик или добавляет элемент в вектор до макроса return_if_io!, при возобновлении операции после завершения ввода-вывода эта мутация выполнится ещё раз. Инструкции скилла требуют менять состояние только после завершения I/O либо хранить шаг выполнения внутри полей перечисления стейт-машины.
Паттерны строго привязаны к модулю core базы Turso, а проверять такой код авторы советуют через детерминированные тесты в testing/simulator/. В проектах на стандартном рантайме Tokio или чистом async/await эти правила бесполезны.
Какие задачи решает
- переписать асинхронную логику на стейт-машину с типом IOResult
- найти мутации состояния перед точками возврата return_if_io!
- объединить несколько операций ввода-вывода через CompletionGroup
- спроектировать варианты enum для безопасного сохранения прогресса
Примеры запросов агенту
- Спроектируй стейт-машину для чтения страниц через IOResult без мутаций до yield
- Проверь эту функцию в core на ошибки повторного входа перед вызовом return_if_io!
- Собери несколько Completion в один вызов с помощью CompletionGroup
Как установить async-io-model
1Через Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a claude-code2Вручную: положить файлы
~/.claude/skills/async-io-model/SKILL.mdПапка скилла целиком; для одного проекта — .claude/skills/async-io-model/
1Через Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a codex2Вручную: положить файлы
.agents/skills/async-io-model/SKILL.mdДля всех проектов — ~/.agents/skills/
1Через Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a cursor2Вручную: положить файлы
.cursor/skills/async-io-model/SKILL.mdCursor читает и .agents/skills/, и .claude/skills/
1GitHub CLI
gh skill install tursodatabase/turso async-io-model2Вручную: положить файлы
.github/skills/async-io-model/SKILL.md1Через Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a gemini-cli2Командой платформы
gemini skills install https://github.com/tursodatabase/turso --path .claude/skills/async-io-modelВручную: положить файлы
.devin/skills/async-io-model/SKILL.mdСтарый путь .windsurf/skills/ тоже читается
Через Skills CLI (Vercel)
npx skills add tursodatabase/turso --skill async-io-model -a opencodeClaude.ai
Упакуйте папку в zip и загрузите в Settings → Capabilities → Skills
Из того же набора10
Весь набор →Похожие
Вопросы о async-io-model
Как установить async-io-model в Claude Code?
Выполните в терминале команду npx skills add tursodatabase/turso --skill async-io-model -a claude-code.
Что делает скилл async-io-model?
Он передаёт языковой модели соглашения ядра Turso по асинхронному вводу-выводу, где вместо async/await используются тип IOResult, макрос return_if_io! и конечные автоматы.
Чем опасны мутации состояния до yield в этой модели?
При возврате варианта IO вызывающая сторона позже запускает функцию повторно. Любое изменение вектора или индекса до прерывания сработает дважды и повредит данные.
Исходный текст
SKILL.md · MIT
---
name: async-io-model
description: Explanations of common asynchronous patterns used in tursodb. Involves IOResult, state machines, re-entrancy pitfalls, CompletionGroup. Always use these patterns in `core` when doing anything IO
---
# Async I/O Model Guide
Turso uses cooperative yielding with explicit state machines instead of Rust async/await.
## Core Types
```rust
pub enum IOCompletions {
Single(Completion),
}
#[must_use]
pub enum IOResult<T> {
Done(T), // Operation complete, here's the result
IO(IOCompletions), // Need I/O, call me again after completions finish
}
```
Functions returning `IOResult` must be called repeatedly until `Done`.
## Completion and CompletionGroup
A `Completion` tracks a single I/O operation:
```rust
pub struct Completion { /* ... */ }
impl Completion {
pub fn finished(&self) -> bool;
pub fn succeeded(&self) -> bool;
pub fn get_error(&self) -> Option<CompletionError>;
}
```
To wait for multiple I/O operations, use `CompletionGroup`:
```rust
let mut group = CompletionGroup::new(|_| {});
// Add individual completions
group.add(&completion1);
group.add(&completion2);
// Build into single completion that finishes when all complete
let combined = group.build();
io_yield_one!(combined);
```
`CompletionGroup` features:
- Aggregates multiple completions into one
- Calls callback when all complete (or any errors)
- Can nest groups (add a group's completion to another group)
- Cancellable via `group.cancel()`
## Helper Macros
### `return_if_io!`
Unwraps `IOResult`, propagates IO variant up the call stack:
```rust
let result = return_if_io!(some_io_operation());
// Only reaches here if operation returned Done
```
### `io_yield_one!`
Yields a single completion:
```rust
io_yield_one!(completion); // Returns Ok(IOResult::IO(Single(completion)))
```
## State Machine Pattern
Operations that may yield use explicit state enums:
```rust
enum MyOperationState {
Start,
WaitingForRead { page: PageRef },
Processing { data: Vec<u8> },
Done,
}
```
The function loops, matching on state and transitioning:
```rust
fn my_operation(&mut self) -> Result<IOResult<Output>> {
loop {
match &mut self.state {
MyOperationState::Start => {
let (page, completion) = start_read();
self.state = MyOperationState::WaitingForRead { page };
io_yield_one!(completion);
}
MyOperationState::WaitingForRead { page } => {
let data = page.get_contents();
self.state = MyOperationState::Processing { data: data.to_vec() };
// No yield, continue loop
}
MyOperationState::Processing { data } => {
let result = process(data);
self.state = MyOperationState::Done;
return Ok(IOResult::Done(result));
}
MyOperationState::Done => unreachable!(),
}
}
}
```
## Re-Entrancy: The Critical Pitfall
**State mutations before yield points cause bugs on re-entry.**
### Wrong
```rust
fn bad_example(&mut self) -> Result<IOResult<()>> {
self.counter += 1; // Mutates state
return_if_io!(something_that_might_yield()); // If yields, re-entry will increment again!
Ok(IOResult::Done(()))
}
```
If `something_that_might_yield()` returns `IO`, caller waits for completion, then calls `bad_example()` again. `counter` gets incremented twice (or more).
### Correct: Mutate After Yield
```rust
fn good_example(&mut self) -> Result<IOResult<()>> {
return_if_io!(something_that_might_yield());
self.counter += 1; // Only reached once, after IO completes
Ok(IOResult::Done(()))
}
```
### Correct: Use State Machine
```rust
enum State { Start, AfterIO }
fn good_example(&mut self) -> Result<IOResult<()>> {
loop {
match self.state {
State::Start => {
// Don't mutate shared state here
self.state = State::AfterIO;
return_if_io!(something_that_might_yield());
}
State::AfterIO => {
self.counter += 1; // Safe: only entered once
return Ok(IOResult::Done(()));
}
}
}
}
```
## Common Re-Entrancy Bugs
| Pattern | Problem |
|---------|---------|
| `vec.push(x); return_if_io!(...)` | Vec grows on each re-entry |
| `idx += 1; return_if_io!(...)` | Index advances multiple times |
| `map.insert(k,v); return_if_io!(...)` | Duplicate inserts or overwrites |
| `flag = true; return_if_io!(...)` | Usually ok, but check logic |
## State Enum Design
Encode progress in state variants:
```rust
// Good: index is part of state, preserved across yields
enum ProcessState {
Start,
ProcessingItem { idx: usize, items: Vec<Item> },
Done,
}
// Loop advances idx only when transitioning states
ProcessingItem { idx, items } => {
return_if_io!(process_item(&items[idx]));
if idx + 1 < items.len() {
self.state = ProcessingItem { idx: idx + 1, items };
} else {
self.state = Done;
}
}
```
## Turso Implementation
Key files:
- `core/types.rs` - `IOResult`, `IOCompletions`, `return_if_io!`, `return_and_restore_if_io!`
- `core/io/completions.rs` - `Completion`, `CompletionGroup`
- `core/util.rs` - `io_yield_one!` macro
- `core/state_machine.rs` - Generic `StateMachine` wrapper
- `core/storage/btree.rs` - Many state machine examples
- `core/storage/pager.rs` - `CompletionGroup` usage examples
## Testing Async Code
Re-entrancy bugs often only manifest under specific IO timing. Use:
- Deterministic simulation (`testing/simulator/`)
- Whopper concurrent DST (`testing/concurrent-simulator/`)
- Fault injection to force yields at different points
## References
- `docs/manual.md` section on I/O
© tursodatabase · MIT · tursodatabase/turso