feat: GASP bridge — tested GASP-conformant runtime (C2b)#68
Merged
Conversation
Phase C2(b) of the roadmap: record agent runs into a GASP agent repo
("the repo is the agent"), with the protocol's conformance checker running
in CI.
- New `gasp` Cargo feature (optional dep: yoagent-state 0.4, the GASP
reference runtime — feather-light: serde/tokio/uuid/chrono, git via
shell). Placement decision: the bridge lives in yoagent because the
reverse direction would force yoagent-state to depend on the whole agent
runtime (reqwest et al.) just for the AgentEvent enum.
- `gasp::GaspRecorder`: consumes the AgentEvent stream via
`recording_sender(task, forward)` — a sender for prompt_with_sender plus
a handle resolving to the RunId; events are teed to an optional forward
sender so the UI keeps streaming. Zero agent-loop changes.
- Mapping: AgentStart→run.started · assistant MessageEnd→model.called/
finished pair · ToolExecutionStart/End→tool.called/finished ·
AgentEnd→run.finished + one git commit per run (append-only history).
Summaries are bounded one-liners — full transcripts belong in GASP's
transcripts/ tier (= Session::to_jsonl).
- Robustness: stale open runs are closed as "interrupted" on open (crashed
process), and a dropped sender without AgentEnd finishes the run as
"interrupted" before committing.
- `GoalRef::{Existing, New}` — runs chain to a caller-managed goal.
- New CI job `gasp-conformance`: emits a repo via examples/gasp_emit.rs
(MockProvider — deterministic, no network) and runs the gasp
conformance checker; all 7 checks verified passing locally
("conformant: all checks passed").
Tests (4): full-run event skeleton in order + commit assertion; tee to
forward sender; goal reuse across recorder reopen (single goal.created);
dropped-sender interrupted-run closure.
Docs: concepts/gasp.md (+ SUMMARY, session-trees cross-link), README
integrations bullet, lib.rs bullet, CLAUDE.md section, CHANGELOG.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Tests set GIT_AUTHOR/COMMITTER env vars (commit_run shells out to plain git commit; runners have no identity — macos images happened to). Verified with GIT_CONFIG_GLOBAL=/dev/null locally. - MSRV job excludes the gasp feature: yoagent-state uses let-chains (stable 1.88) despite declaring rust-version 1.85 — follow-up upstream. Documented in the gasp docs page. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four-agent review; the code reviewer cloned the emitted repo and re-ran the conformance checker, proving check 6 passed only on the emitting worktree. Critical: - Restore fixed: init/open now commit the scaffolding (AGENT.md, identity/, .gitignore) plus pre-run events (goal.created, stale-run closure) via commit_scaffolding, so a fresh `git clone` is a complete conformant agent. Verified locally: checker passes against a CLONE. CI now clones the emitted repo and checks the clone (the actual GASP restore), and pins the gasp checker to a rev so upstream changes can't break yoagent CI silently. - Consumer resilience: events are forwarded to the UI tee BEFORE recording; a mid-run StateError logs (tracing::error!), stops recording, and keeps draining + forwarding — a recorder failure never blinds the UI. The error surfaces via the handle (documented as the only error channel). Contract fixes: - recording_sender resolves to Option<RunId>: Ok(None) when AgentStart never arrived, instead of fabricating an id that exists nowhere in the log. - Dropped-sender fallback closes the run with the DERIVED outcome (was hardcoded "interrupted", disagreeing with the commit trailer). - InputRejected runs record outcome "rejected" (was indistinguishable from a crash in the durable log). - GoalRef::Existing validated at open — dangling goal ids fail loudly. - release_lease after each run (was held for the full 600s TTL, locking out the next worker/process). - with_summarizer redaction hook: summaries of tool inputs/outputs persist to a shareable repo — now redactable; docs warn explicitly. Comment/doc corrections (review-verified): "chained to your goal" → Goal: commit trailer; dropped-sender comment listed impossible triggers (filter reject/abort DO send AgentEnd); stale-run comment described the mechanism backwards (marker is in-memory; resume_open_run restores it); "exactly the shape of transcripts/" → "a natural format for" (spec leaves it open); example's verify command now shows the real clone+cargo-run invocation; sequential-runs + single-writer constraints documented. Tests 4 → 12 (+1 unit): fresh-clone restore (manifest/identity/log); sink-failure keeps tee alive through AgentEnd + surfaces Err (unix, read-only log); crash recovery via handle.abort() then reopen + record again; Ok(None) no-run; all four stop_reason→outcome mappings; InputRejected → "rejected"; dangling goal rejection; tee completeness (kind-sequence equality vs a direct run); summarize char-boundary unit test; skeleton test switched to allowlist filtering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 11, 2026
yuanhao
added a commit
to yologdev/yoagent-state
that referenced
this pull request
Jul 11, 2026
* ci: add CI with an MSRV-enforcement job The repo had no PR CI at all — only the publish workflow. Two jobs: - Check & Test (stable): fmt --check, clippy --all-targets under -Dwarnings, cargo test (with a git identity for the git_store tests). - MSRV (1.85): cargo check --all-targets on the DECLARED rust-version. Cargo never verifies rust-version against the code — 0.4.0/0.4.1 shipped a let-chain (needs Rust 1.88) while declaring 1.85, silently breaking downstream MSRV builds (yologdev/yoagent#68). This job makes that class of drift impossible: any future construct above 1.85 fails the PR. Also brings the tree up to the new gates: cargo fmt over src/state.rs, and the test-side DoubleEndedIterator lint fixed (.filter().last() -> .rfind()). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix: collapse resume_open_run's nested if into a match guard The CI runner's newer clippy (1.97) flags collapsible_match here. Safe: guard fall-through lands on the catch-all no-op arm — identical semantics to the nested if (verified by the run_chaining tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * ci: enforce the committed lock (--locked), concurrency, permissions, timeouts Review findings: uuid/getrandom sit at exactly rust-version 1.85 — without --locked, a lock regeneration could pull a 1.86-MSRV patch and silently break (or mask regressions in) the 1.85 gate. Also: cancel superseded runs, least-privilege token, and job timeouts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Phase C2(b): yoagent records agent runs into a GASP agent repo — and backs the claim with the protocol's own conformance checker running in CI. This completes the C2 pair: session trees (C2a) are the
transcripts/tier; this is the semantic event log.Design
AgentEventstream (the_with_senderAPI existing for exactly this), teeing events to your UI.gaspfeature in yoagent depending onyoagent-state0.4 (the GASP reference runtime, which already ships theYoAgentStateSink/git-store machinery). The reverse direction would force yoagent-state to depend on the whole agent runtime for one enum.run.started→model.called/finishedper turn →tool.called/finishedper execution →run.finished+ one git commit per run (append-only history is what conformance check 4 walks). Summaries only — transcripts belong in the cold tier (Session::to_jsonl).interruptedon open; a dropped stream finishes the run asinterruptedbefore committing.The proof
New CI job
gasp-conformance: emits a repo with a MockProvider agent (deterministic, no network, no keys) and runs the gasp checker against it. Verified locally:That makes the roadmap's flagship claim literal: yoagent is the first tested GASP-conformant runtime — the yologdev narrative assembled (yoagent the runtime, GASP the protocol, yoyo the proof).
Tests (4 new, 381 total green)
Full-run event skeleton asserted in order + commit assertion · tee to forward sender · goal reuse across recorder reopen · dropped-sender interrupted closure.
Verification
clippy
-Dwarnings --all-featuresclean · 381 passed / 0 failed · docs-Dwarningsclean · conformance checker passes locally against the emitted repo.🤖 Generated with Claude Code