-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add polyglot agent framework adapters #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,8 @@ | ||
| .venv/ | ||
| .worktree/ | ||
| .worktrees/ | ||
| node_modules/ | ||
| *.tsbuildinfo | ||
| .mypy_cache/ | ||
| .pytest_cache/ | ||
| .ruff_cache/ | ||
|
|
||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,20 @@ | ||
| .PHONY: fix lint test build | ||
|
|
||
| fix: | ||
| uv run ruff format . | ||
| uv run ruff check --fix . | ||
| $(MAKE) -C python fix | ||
| $(MAKE) -C go fix | ||
|
|
||
| lint: | ||
| uv run ruff format --check . | ||
| uv run ruff check . | ||
| uv run mypy | ||
| $(MAKE) -C python lint | ||
| $(MAKE) -C typescript lint | ||
| $(MAKE) -C go lint | ||
|
|
||
| test: | ||
| uv run pytest | ||
| $(MAKE) -C python test | ||
| $(MAKE) -C typescript test | ||
| $(MAKE) -C go test | ||
|
|
||
| build: | ||
| uv build | ||
|
|
||
| $(MAKE) -C python build | ||
| $(MAKE) -C typescript build | ||
| $(MAKE) -C go build |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,84 +1,71 @@ | ||
| # Agent Ledger | ||
|
|
||
| Agent Ledger is a framework-neutral event ledger for durable agent sessions. It records agent | ||
| steps before model and tool execution, keeps causal links across distributed agent runs, and gives | ||
| framework adapters enough facts to rebuild their own run context after a restart. | ||
|
|
||
| The ledger is the source of truth for **what happened**. Recovery remains owned by the framework | ||
| integration that understands its checkpoint and `RunContext` types. | ||
|
|
||
| ## Why another event log? | ||
|
|
||
| Traditional logs and traces explain service execution. Agent Ledger adds agent-native invariants: | ||
|
|
||
| - `Session` groups one end-to-end task across processes and agents. | ||
| - `Run` is the optimistic-concurrency stream written by one agent loop. | ||
| - `Step` is a logical unit; `Attempt` is one physical model or tool invocation. | ||
| - requested events are committed before external calls, so interrupted calls remain visible. | ||
| - `parent_run_id` and `caused_by_event_id` form a causal DAG without relying on timestamps. | ||
| - trajectories such as ATIF are projections, not the durable source of truth. | ||
|
|
||
| ## Quick start | ||
|
|
||
| ```python | ||
| from agent_ledger import Actor, SessionRecorder | ||
| from agent_ledger.stores.memory import MemoryEventStore | ||
|
|
||
| store = MemoryEventStore() | ||
| recorder = SessionRecorder( | ||
| store=store, | ||
| session_id="session-1", | ||
| run_id="run-1", | ||
| actor=Actor(type="agent", id="researcher"), | ||
| ) | ||
|
|
||
| await recorder.start_run(payload={"task": "summarize"}) | ||
| attempt = await recorder.before_model_call( | ||
| step_id="step-1", | ||
| payload={"model": "example-model", "messages": [{"role": "user", "content": "Hi"}]}, | ||
| ) | ||
|
|
||
| # The real model call starts only after model.requested is durably appended. | ||
| response = await model.generate() | ||
| await recorder.model_completed(attempt, payload={"message": response}) | ||
| Agent Ledger is a framework-neutral specification and a set of polyglot adapters for durable agent | ||
| sessions. It records model and tool attempts before execution, preserves causal timelines across | ||
| distributed agents, and lets each framework rebuild its own native session after interruption. | ||
|
|
||
| The specification is the stable product. Language SDKs are deliberately small; most project code | ||
| lives in adapters that understand a framework's hooks, messages, checkpoints, and resume APIs. | ||
|
|
||
| ## Model | ||
|
|
||
| - `Session` groups one end-to-end task across processes, languages, and agents. | ||
| - `Run` identifies one semantic agent execution and participates in the causal DAG. | ||
| - `EventStream` is an optimistic-concurrency partition. It may contain one run's execution events or | ||
| framework-native state that survives several runtime runs. | ||
| - `Step` is logical work that survives retries; `Attempt` is one physical model or tool invocation. | ||
| - Normalized events are the source for timelines and trajectories. Framework-native records are the | ||
| source for resume. | ||
|
|
||
| Requested events are committed before an external call. A requested event without a terminal event | ||
| is unresolved after a crash. It is input to the adapter's reconciliation policy; an adapter must | ||
| not silently replay a side-effecting tool. | ||
|
|
||
| ## Repository | ||
|
|
||
| | Area | Responsibility | | ||
| | --- | --- | | ||
| | `spec/` | Event, append, adapter capability, and recovery contracts | | ||
| | `conformance/` | Cross-language golden vectors and adapter contract tests | | ||
| | `python/` | Python core SDK plus memory, Redis, and SQLAlchemy stores | | ||
| | `typescript/` | TypeScript core SDK and Pi adapter | | ||
| | `go/` | Go core SDK and AgentGo adapter | | ||
|
|
||
| Current framework profiles: | ||
|
|
||
| | Adapter | Recording | Recovery | | ||
| | --- | --- | --- | | ||
| | Pi AgentHarness | Awaited model/tool hooks | Ledger-backed Pi `SessionStorage` | | ||
| | AgentGo | `ChatModel` wrapper, turn hooks, message committer, tool middleware | Native message codec with `HoldRuns` + `SetMessages` + `Continue` | | ||
| | Plain Python loop | Explicit recorder calls | Snapshot plus tail replay | | ||
|
|
||
| Every adapter publishes machine-readable capabilities such as `strict`, `best_effort`, and | ||
| `unsupported`; installing a telemetry-only hook never silently claims durable recovery. | ||
|
|
||
| ## Store contract | ||
|
|
||
| Applications inject an `EventStore`. V1 has no mandatory collector or `/agent-session` service: | ||
| framework processes write directly to an in-memory, Redis, or database implementation selected by | ||
| the host. | ||
|
|
||
| ```text | ||
| append(stream, expected_version, append_id, events) | ||
| read_stream(stream, after_version) | ||
| scan_session(session_id, after_cursor) | ||
| ``` | ||
|
|
||
| If the process stops after `before_model_call`, inspection reports an unresolved attempt. An adapter | ||
| can then ask the provider for a result, require human confirmation, or retry with a new | ||
| `attempt_id`; the generic library never silently repeats an external side effect. | ||
|
|
||
| ## Stores | ||
|
|
||
| `EventStore` has three implementations: | ||
|
|
||
| - `MemoryEventStore`: process-local reference implementation and test double. | ||
| - `RedisEventStore`: atomic append through Lua, with per-session cluster key co-location. | ||
| - `SqlEventStore`: one SQLAlchemy 2.x implementation for SQLite, MySQL, and PostgreSQL. | ||
|
|
||
| Redis and SQL clients are supplied by the application so pool size, connection timeout, and | ||
| deployment-specific durability are explicit. Install optional dependencies with | ||
| `agent-ledger[redis]`, `agent-ledger[sql]`, `agent-ledger[mysql]`, or | ||
| `agent-ledger[postgres]`. | ||
|
|
||
| ## Design boundaries | ||
|
|
||
| - No collector or mandatory network service in v1. | ||
| - No generic cross-framework `RunContext` serializer. | ||
| - No automatic replay of an unresolved tool side effect. | ||
| - No exactly-once claim. Appends are atomic and idempotent; external calls are not transactional | ||
| with the ledger. | ||
| - No global ordering claim. `commit_cursor` orders one session's stored events for display and | ||
| pagination; causal links define execution relationships. | ||
|
|
||
| See [RFC 0001](spec/rfcs/0001-agent-ledger.md) for the contract and | ||
| [the plain-loop example](examples/plain_loop.py) for adapter-owned recovery. | ||
| Appends are atomic, idempotent by RFC 8785 canonical event content, and protected by optimistic | ||
| concurrency. `commit_cursor` gives one session a display/pagination order; causal links, not cursor | ||
| or timestamps, define execution relationships. | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| uv sync --all-extras | ||
| make fix | ||
| make lint | ||
| make test | ||
| make build | ||
| ``` | ||
|
|
||
| See [RFC 0001](spec/rfcs/0001-agent-ledger.md) for the ledger contract and | ||
| [RFC 0002](spec/rfcs/0002-polyglot-adapters.md) for framework recording and recovery boundaries. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # Conformance | ||
|
|
||
| Language SDKs consume the same golden vectors to verify event encoding and append identity. Adapter | ||
| contract suites live beside their implementations. The conformance target includes failure | ||
| injection before model calls, before tools, after external outcomes, and during native-state | ||
| restoration. | ||
|
|
||
| Passing these vectors is required before an SDK can claim Agent Ledger schema `1.x` compatibility. |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "canonicalization": "RFC8785", | ||
| "canonical": "[{\"actor\":{\"framework\":\"pi\",\"id\":\"writer\",\"type\":\"agent\"},\"attempt_id\":\"attempt-1\",\"event_id\":\"event-1\",\"event_type\":\"model.requested\",\"extensions\":{},\"occurred_at\":\"2026-01-02T03:04:05Z\",\"payload\":{\"model\":\"claude\",\"temperature\":0.25,\"tokens\":1024},\"run_id\":\"run-1\",\"schema_version\":\"1.0\",\"session_id\":\"session-1\",\"step_id\":\"step-1\"}]", | ||
| "sha256": "b83e0ae0fcf7e76613c66f34b6c034cb0d35e517271aea5cf6c8820ac7ef5721", | ||
| "events": [ | ||
| { | ||
| "schema_version": "1.0", | ||
| "event_id": "event-1", | ||
| "event_type": "model.requested", | ||
| "session_id": "session-1", | ||
| "run_id": "run-1", | ||
| "actor": { "type": "agent", "id": "writer", "framework": "pi" }, | ||
| "occurred_at": "2026-01-02T03:04:05Z", | ||
| "step_id": "step-1", | ||
| "attempt_id": "attempt-1", | ||
| "payload": { "model": "claude", "temperature": 0.25, "tokens": 1024 }, | ||
| "extensions": {} | ||
| } | ||
| ] | ||
| } |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| .PHONY: fix lint test build | ||
|
|
||
| fix: | ||
| go fmt ./... | ||
|
|
||
| lint: | ||
| go vet ./... | ||
|
|
||
| test: | ||
| go test ./... | ||
|
|
||
| build: | ||
| go build ./... |
Oops, something went wrong.
Oops, something went wrong.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤖 devloop code-review · deepseek-v4-pro · ready in 1m 44s
Both the new
typescriptandgojobs lack atimeout-minutessetting. If a test hangs (e.g., infinite loop, deadlocked async operation, or amake testthat waits forever on a missing service), the job will run until GitHub's default 6-hour timeout, wasting runner minutes and delaying other queued workflows.Add
timeout-minutes: 10(or whatever is appropriate for these test suites) to each job:ccr:fp=55e1a7720aef
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ccr:label=debatable — GitHub Actions 已有 6 小时硬上限,仓库现有 Python job 同样未设 timeout-minutes;为新 job 增加更短预算是运维偏好,不构成功能或可靠性缺陷 #padding