Skip to content

Commit 81f2027

Browse files
committed
clean up
1 parent 6a6453a commit 81f2027

13 files changed

Lines changed: 839 additions & 140 deletions

File tree

.github/workflows/ci.yml

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,58 @@ jobs:
1414
with:
1515
bun-version: latest
1616
- run: bun install --frozen-lockfile
17+
- run: bun run build # build first — dependents resolve oyadotai from dist/
1718
- run: bun run typecheck
1819
- run: bun test
19-
- run: bun run build
20+
21+
# Every push to main that passes `build` cuts a new patch release of both
22+
# libraries to npm's `latest` tag, then commits the version bump + tag back.
23+
# The bump commit carries [skip ci] and is pushed with GITHUB_TOKEN, so it
24+
# does not re-trigger this workflow (no publish loop).
25+
publish:
26+
needs: build
27+
if: >-
28+
github.event_name == 'push' &&
29+
github.ref == 'refs/heads/main' &&
30+
!contains(github.event.head_commit.message, '[skip ci]')
31+
runs-on: ubuntu-latest
32+
permissions:
33+
contents: write
34+
steps:
35+
- uses: actions/checkout@v4
36+
with:
37+
ref: main
38+
fetch-depth: 0
39+
- uses: oven-sh/setup-bun@v2
40+
with:
41+
bun-version: latest
42+
- uses: actions/setup-node@v4
43+
with:
44+
node-version: 20
45+
registry-url: https://registry.npmjs.org
46+
- run: bun install --frozen-lockfile
47+
48+
- name: Bump patch versions
49+
run: |
50+
(cd packages/core && npm version patch --no-git-tag-version)
51+
(cd packages/server && npm version patch --no-git-tag-version)
52+
53+
- run: bun install # sync bun.lock with the bumped versions
54+
- run: bun run build # rebuild dist/ before publishing
55+
56+
- name: Publish to npm
57+
env:
58+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
59+
run: |
60+
(cd packages/core && npm publish --access public)
61+
(cd packages/server && npm publish --access public)
62+
63+
- name: Commit release + tag
64+
run: |
65+
VERSION=$(node -p "require('./packages/core/package.json').version")
66+
git config user.name "github-actions[bot]"
67+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
68+
git add packages/core/package.json packages/server/package.json bun.lock
69+
git commit -m "release: v$VERSION [skip ci]"
70+
git tag "v$VERSION"
71+
git push --follow-tags

DEMO.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Capturing the demo GIF
2+
3+
`demo.gif` (shown in the README hero) is a recording of the paced, deterministic
4+
"plan, don't react" terminal demo in
5+
[`packages/core/examples/demo.ts`](./packages/core/examples/demo.ts). It runs
6+
against a canned local model — **no API key, same output every time** — so the GIF
7+
is reproducible.
8+
9+
## Regenerate it
10+
11+
```bash
12+
brew install vhs # one-time; pulls ttyd + ffmpeg
13+
vhs demo.tape # writes demo.gif at the repo root
14+
```
15+
16+
The recording is scripted in [`demo.tape`](./demo.tape) (size, theme, timing). To
17+
preview the demo without recording:
18+
19+
```bash
20+
make demo # or: cd packages/core && bun run demo
21+
```
22+
23+
## Swapping in the web Studio
24+
25+
To feature the live web Studio (the DAG + trace UI) instead of the terminal demo,
26+
record `bunx oyadotai dev` (Studio at `localhost:4000`) with any screen recorder,
27+
export a GIF, save it as `demo.gif`, and the README hero picks it up automatically.

README.md

Lines changed: 57 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,28 @@
22

33
# oya
44

5-
### Agents that **plan, don't react.**
5+
### Your agent re-feeds every tool result back through the model. **That's the bug.**
66

7-
The model writes a typed plan **once**. The runtime runs it. Tool outputs never go
8-
back through the model — so your agent **can't be prompt-injected through its tools,
9-
costs a fraction, and runs the same every time.**
7+
Every framework — ReAct, LangGraph, Mastra, the Vercel AI SDK — loops the model on
8+
each step: it reads a tool's output, re-types it as the next tool's arguments, and
9+
pays for the round trip. oya compiles a **typed plan once** and executes the DAG.
10+
Values flow by reference, never back through the model.
1011

11-
**Drop-in for Mastra.** TypeScript · Bun · MIT
12+
Same code. Same tools. **10× fewer tokens · 3.5× faster · deterministic · injection-safe by construction.**
1213

13-
[Quickstart](#quickstart) · [The numbers](#the-numbers) · [Why](#why) · [Migrate from Mastra](#migrate-from-mastra-in-2-lines) · [Studio](#studio)
14+
[![npm](https://img.shields.io/npm/v/oyadotai?color=black&label=oyadotai)](https://www.npmjs.com/package/oyadotai)
15+
 ·  TypeScript · Bun · MIT ·  **Drop-in for Mastra**
16+
17+
[Quickstart](#quickstart) · [The numbers](#the-numbers) · [Why](#why) · [Migrate in 2 lines](#migrate-from-mastra-in-2-lines) · [Studio](#studio)
1418

1519
</div>
1620

17-
<!-- LAUNCH: drop the studio GIF here before publishing — capture it with DEMO.md -->
18-
<p align="center"><img src="./studio.gif" width="820" alt="oya Studio — a plan executing live: the DAG, the trace, and each value at its projection level"></p>
21+
<!-- Terminal demo (deterministic, no API key). Regenerate with `vhs demo.tape`; see DEMO.md. -->
22+
<p align="center">
23+
<img src="./demo.gif" width="860" alt="oya executing a plan: get_weather and summarise are TRANSPARENT, while generate_pdf and generate_webpage stay OPAQUE — hidden from the model, which never reads them.">
24+
</p>
25+
26+
<div align="center"><sub><code>bun run bench</code> · reconcile · haiku · 8 trials — <b>oya: 2,536 tokens vs Mastra 24,387 (10×) · 4.7s vs 17.6s (3.5×) · deterministic</b></sub></div>
1927

2028
---
2129

@@ -47,18 +55,31 @@ and it's injection-safe.
4755

4856
## The numbers
4957

50-
Same task, same tools, real Anthropic API, 3 trials on `claude-opus-4-7`:
58+
A token loop re-sends every tool result back through the model on the following
59+
step, spending tokens, latency, and determinism to move state it never needed to
60+
read. oya compiles the plan once and wires each value by reference. The
61+
`reconcile` task — get a transaction → fetch its record → normalize → validate →
62+
post — measures the difference on the real Anthropic API (`claude-haiku-4-5`,
63+
identical tools, 8 trials):
5164

5265
| | Vercel AI SDK | Mastra | **oya** |
5366
|---|--:|--:|--:|
54-
| total tokens | 3,653 | 9,143 | **1,783** |
55-
| model round-trips | 4 | 4 | **2** |
56-
| latency | 20.0s | 20.2s | **6.3s** |
57-
| same plan every run? | no | no | **yes** |
58-
59-
**½ the tokens of the leanest loop, 5× fewer than Mastra, ~3× faster — and
60-
deterministic.** (The gap narrows on smaller/cheaper models and widens with bigger
61-
payloads — full methodology and numbers in [`benchmarks/`](./benchmarks).)
67+
| total tokens | 5,754 | 24,387 | **2,536** |
68+
| model round-trips | 6 | 6 | **2** |
69+
| latency | 17.6s | 15.3s | **4.7s** |
70+
| execution order | model-chosen | model-chosen | **one fixed DAG** |
71+
| state fidelity | unguaranteed | unguaranteed | **guaranteed** |
72+
73+
**oya delivers the same result with 2.3× fewer tokens than the leanest loop, nearly
74+
10× fewer than Mastra, in a third of the wall-clock time — and it does so
75+
identically on every run.** The record's bulky payload re-enters a loop's context
76+
at every step; under oya it remains an `OPAQUE` handle the model never sees.
77+
78+
State fidelity is a **guarantee, not an average.** Every value flows by reference
79+
as an `OPAQUE` handle that the model never re-reads or re-emits, so a URL, id, or
80+
amount is delivered to the next tool byte-for-byte and the execution order is the
81+
statically-checked DAG — outcomes a token loop can only approximate, one sampled
82+
run at a time. Reproduce it yourself: `bun run bench`.
6283

6384
### Verify it yourself
6485

@@ -79,20 +100,27 @@ bun run build
79100
export ANTHROPIC_API_KEY=sk-ant-...
80101

81102
# 4. run the comparison
82-
bun run bench # default: research task, Haiku, 3 trials
83-
bun run bench --task weather claude-opus-4-7 # the headline run from the table above
103+
bun run bench # default: reconcile, claude-haiku-4-5, 3 trials
104+
bun run bench claude-sonnet-5 # any model id as the first arg
105+
bun run bench --task research # the heavy multi-doc case
84106
```
85107

86-
Args go in any order: a **model id** (defaults to Haiku), a **trial count**
87-
(integer, defaults to `3`), and `--task weather` or `--task research` (defaults to
88-
`research`). Prefer `make bench` — it runs the build for you and auto-loads a
89-
`.env` at the repo root, so steps 2–4 collapse into one command.
90-
91-
Each framework runs the task N times against the same model and prints
92-
**mean ± stddev** — the stddev is the point: oya's token cost and tool order barely
93-
move, while the token loops swing (Mastra's total swung ±4797 in our run). Token
94-
loops are stochastic, so **run it a few times**; your absolute numbers will differ
95-
by model and payload. Full methodology, caveats, and where the gap narrows are in
108+
Args go in any order: a **model id** (defaults to `claude-haiku-4-5-20251001`), a
109+
**trial count** (integer, defaults to `3`), and `--task reconcile` / `--task payments`
110+
/ `--task research` / `--task weather` (defaults to `reconcile`). Prefer
111+
`make bench` — it runs the build for you and auto-loads a `.env` at the repo root,
112+
so steps 2–4 collapse into one command.
113+
114+
The default `reconcile` task threads a critical token through a multi-hop pipeline
115+
whose fetched record is a bulky payload carrying a look-alike distractor. It
116+
measures **token waste** (that payload re-enters a loop's context at every step,
117+
while oya keeps it `OPAQUE`), **state fidelity** (a provenance ledger confirms the
118+
token reaches the final tool byte-for-byte), and **ordering** (a statically-checked
119+
DAG). oya returns `0` corruption and one fixed order on every run, by construction.
120+
`--task research` extends the comparison to a heavy multi-document workload.
121+
122+
Each framework runs the task N times against the same model, and prints tokens,
123+
latency, and correctness side by side. The methodology is in
96124
[`benchmarks/README.md`](./benchmarks/README.md).
97125

98126
## Why

0 commit comments

Comments
 (0)