Skip to content

Add Anthropic SDK tool loop integration - #323

Open
cosmo-lgtm wants to merge 1 commit into
agentrust-io:mainfrom
cosmo-lgtm:anthropic-tool-loop
Open

cosmo-lgtm wants to merge 1 commit into
agentrust-io:mainfrom
cosmo-lgtm:anthropic-tool-loop

Conversation

@cosmo-lgtm

Copy link
Copy Markdown

Adds integrations/anthropic-tool-loop/: an integration for agents built directly on the anthropic Python SDK (a client.messages.create tool-use loop), which the index does not cover today.

What it does

  • spool.py: a standard-library writer with a small Run API (six calls in the loop). It appends metadata-only rows: model calls, tool calls, the caller's gate decision with a reason code, a sha256 digest of each tool input, and timings. It runs on Python 3.9+ and never raises into the agent's reply.
  • recorder.py: an out-of-process recorder that turns those rows into AgenTrust Telemetry events (policy.decision, action.executed, usage.recorded with an agent_run rollup, data_flow.observed), optional OpenTelemetry spans with the real timestamps, a chained evidence log, and one signed TRACE record per finished run (origin.kind: log-import).
    • A run that never finishes is sealed incomplete and is not signed.
    • The tool-to-system catalog and its classifications are supplied by the caller.
  • An offline example (a scripted client, no network or API key) runs a two-turn loop with one gate deny and one allowed write. It signs and verifies both records and shows an edited copy failing verification.

Validation

  • 28 tests on Python 3.11, 3.12 and 3.13; the writer tests also run on 3.9.
  • An interop test against anthropic 0.69.0, 1.12.1, 1.13.0 and 1.14.0.
  • trace-tests verify --level 0: PASS.
  • The steps in validate.yml, the pinned ruff gate, and actionlint on the new workflow.

Notes from integrating (feedback, not part of the change)

  • A tool call the gate refused and a violation that actually executed both appraise as contraindicated. A separate state for "the gate held" would make ledgers easier to read.
  • finalize_trace needs at least one policy decision, so a plain question-and-answer run cannot be sealed. We record the agent's toolset as a run-level decision.
  • Tool results sent on to the model provider have no policy step of their own. We record that flow with policy_decision: not_evaluated.
  • usage.recorded carries one timestamp, so the model call's start time lives only in the span.
  • agentrust-telemetry is not on PyPI yet. This package pins 0.1.0a6 by commit, and integration.yaml has no field to declare it.

Tier is for maintainers to set; it is listed as community.

🤖 Generated with Claude Code

Records a tool loop written directly against the anthropic Python SDK
as AgenTrust Telemetry events (policy decisions, actions, usage, data
flows) and signs one log-import TRACE record per finished run. The
in-process writer is standard library only and fail-open; the recorder
runs out of process. Metadata only; unfinished runs are not signed.

Includes an offline example, tests (writer, recorder, interop against
the released anthropic SDK with a mocked transport), a path-scoped
conformance workflow running trace-tests at Level 0, and the
regenerated index and Marketplace catalog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@noah-ing noah-ing left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed 5bdbad6ec05816030cbd6ba08a09b1c9cf3d05f6 in an isolated checkout. Thanks for keeping the offline SDK test and log-import limitations explicit.

The normal paths pass locally: 28 integration tests (including Anthropic 1.14.0 interop), 28 repository validation tests, the pinned Ruff gate, manifest/compatibility/catalog checks, the offline example and TRACE Level 0. The sdist/wheel build, fresh-wheel writer import, diff check and merge simulation against main 5612f397 also pass. Local execution was Python 3.12 on macOS; I did not reproduce the whole hosted OS/Python matrix.

Requesting changes for the two recorder-recovery cases below, both reproduced separately from the existing tests. One malformed timestamp prevents unrelated valid runs from being processed, and a replaced spool can silently lose a new run. Focused regressions for those cases would close the gaps.

Separately, the current link-check failure is an HTTP 503 in an unchanged README; I have not treated that as a defect introduced here.

Comment on lines +210 to +215
for run_id, rows in list(self.pending.items()):
ended = any(r["kind"] == "run.end" for r in rows)
last = max(int(r.get("end") or r.get("t") or 0) for r in rows)
if ended or final or now - last > self.idle_seconds * 1_000_000_000:
try:
self.record(rows, complete=ended)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could the timestamp conversion be included in the per-run validation/error boundary? A run.start row with a valid run ID but t="not-a-timestamp", followed by an otherwise valid completed run, raises ValueError here before the try. The bad run remains pending, the valid run is never recorded, and a second poll() raises again. The existing malformed-run test misses this because its timestamps are valid. Please add that case and ensure rejecting the bad run still lets healthy runs progress.

Comment on lines +186 to +190
if self.spool.exists():
if self.spool.stat().st_size < self.offset: # rotated or truncated
self.offset = 0
with self.spool.open("rb") as fh:
fh.seek(self.offset)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The size check does not detect a spool replaced by a new file of equal or greater length. I recorded a valid 560-byte run, renamed its spool aside, and put a new valid 560-byte run at the same path. The next poll() returned [] and the second run was never recorded: the old offset was applied to the new file. Please track file identity/a rotation generation alongside the offset, or define an explicit safe rotation handoff, with an equal-or-larger replacement regression.

@imran-siddique imran-siddique left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Head is still 5bdbad6 and both of Noah's cases reproduce here. A run.start with t "not-a-timestamp" makes poll() raise ValueError, again from a fresh Recorder. A replacement spool of the same 1310 bytes returns [] and the new run is lost. CI steps pass on Python 3.12: 27 passed, 1 skipped, interop on anthropic 1.14.0, Level 0 PASS. The gate waits on maintainer approval; the links 503 is an unrelated README.

PR description: agentrust-telemetry 0.1.0a6 is on PyPI and the workflow installs it from there.

governance events, and signs one TRACE v0.2 record per finished run. There is no
framework to hook into here, so the loop calls the recorder explicitly.

The code this package was generalised from runs in production in front of two

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The production claim (two Slack agents) can't be checked from this repo. Link something a reader can open, or drop it.

self.record(rows, complete=ended)
except Exception:
_log.exception("recording %s failed; its rows are dropped", run_id)
done.append(run_id)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.append(run_id) runs after a failed record() too, so poll() lists a dropped run as recorded, against the docstring at line 184.

This branch has not been deployed

No deployments
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.

3 participants