Skip to content

feat: GASP bridge — tested GASP-conformant runtime (C2b)#68

Merged
yuanhao merged 3 commits into
mainfrom
feat/gasp-bridge
Jul 11, 2026
Merged

feat: GASP bridge — tested GASP-conformant runtime (C2b)#68
yuanhao merged 3 commits into
mainfrom
feat/gasp-bridge

Conversation

@yuanhao

@yuanhao yuanhao commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

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.

let recorder = GaspRecorder::init("./my-agent-repo", "my-agent", "worker-1",
    GoalRef::New { title: "ship the feature".into() }).await?;

let (tx, record_handle) = recorder.recording_sender("implement the parser", None);
agent.prompt_with_sender("implement the parser", tx).await;
let run_id = record_handle.await??;   // events appended + committed

Design

  • Zero loop changes — the recorder is a consumer of the AgentEvent stream (the _with_sender API existing for exactly this), teeing events to your UI.
  • Placement: gasp feature in yoagent depending on yoagent-state 0.4 (the GASP reference runtime, which already ships the YoAgentStateSink/git-store machinery). The reverse direction would force yoagent-state to depend on the whole agent runtime for one enum.
  • Mapping: run.startedmodel.called/finished per turn → tool.called/finished per 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).
  • Crash-safe: stale open runs closed as interrupted on open; a dropped stream finishes the run as interrupted before 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:

[PASS] check 1 — envelope round-trip     [PASS] check 5 — causation integrity
[PASS] check 2 — replay                  [PASS] check 6 — restore
[PASS] check 3 — vocabulary              [PASS] check 7 — domain↔ops consistency
[PASS] check 4 — append-only in git
conformant: all checks passed

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-features clean · 381 passed / 0 failed · docs -Dwarnings clean · conformance checker passes locally against the emitted repo.

🤖 Generated with Claude Code

yuanhao and others added 3 commits July 11, 2026 01:11
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>
@yuanhao yuanhao merged commit a2dabae into main Jul 11, 2026
5 checks passed
@yuanhao yuanhao deleted the feat/gasp-bridge branch July 11, 2026 00:18
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>
@yuanhao yuanhao mentioned this pull request Jul 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant