Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions .agent/handoff/243.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Handoff note: issue #243

- issue: 243
- started_at: 2026-10-14T13:48:04.076479Z
- reason: token_soft_threshold
- last_work_commit: 24d85f3236730f040cec81b2646e6aba687da873

## Summary

Implemented the last-finished attempt telemetry end to end on branch
`agent/issue-243` (work uncommitted; sealing will commit it).

- `simple_coding_agent/telemetry.py`: `finish_attempt(summary=None)` normalizes
and stores `last_finished` (`attempt_id`, `issue_number`, `issue_title`,
`outcome`, `finished_at`, `budget_used`/`turns` as `measured` figures,
`pull_request: {number, url} | null`); legacy summaries seed title
`"unknown"` and `pull_request: null`; malformed summaries log once and keep
the previous view. `snapshot()` now serves `last_finished`. Added
`load_last_finished(data_dir)` built from the latest completed attempt
archive record.
- `simple_coding_agent/observability.py`: added `read_latest_completed_attempt`
scanning `data_dir/logs/<issue>/<started>/attempt.json` (skips
unreadable/incomplete records; latest by `(completed_at, started_at)`).
- `simple_coding_agent/lifecycle.py`: new `data_dir` constructor param with
startup seeding (`_seed_telemetry_last_finished`, never raises);
`_telemetry_finish(summary)` passes summaries through;
`_archive_pull_request` writes the PR into the archive at publication
(hooked into `_publish_bounded`, `_publish_terminal`, recovery retry);
`_commit_finalization` writes issue number/title into the outcome record,
backfills the PR safety net, and finishes telemetry with the full summary;
`_write_outcome` now returns `completed_at`.
- `tests/fakes.py`: `FakePublisher`/`PublishedAttempt` accept `pull_request`.
- `simple_coding_agent/__main__.py`: passes `data_dir` to `AgentLifecycle`.
- New `tests/test_telemetry_last_finished.py` (15 tests): reporter shape,
legacy/unknown handling, unavailable figures, malformed-summary behavior,
finalisation wiring + archive PR, restart seeding, null with no history,
latest-wins seeding, alongside-active reporting.
- `code-review` skill invoked this run: Standards (0 hard violations,
4 smells, worst = duplicated PR normalizer across telemetry/lifecycle) and
Spec (0 missing, 0 creep, 3 wrong-looking, worst = shared
`_build_failed_logged` flag) reports aggregated in the run transcript.

## Remaining work

1. Re-run `tests/test_telemetry_last_finished.py` (was 14/15 green; fixed the
malformed-summary log-count expectation from 2 to 1 to match the
log-once-per-episode pattern; re-run was blocked by the budget hook).
2. Decide on review findings: extract one shared PR normalizer (strongest
Standards finding); consider separate warning flags for snapshot vs finish
failures, timestamp parsing instead of string ordering in the archive scan,
and narrowing the `except TypeError: finish()` shim.
3. Run `ruff check`, full `pytest` on both gate Pythons (3.12 and 3.13).
4. Small docs touch: `docs/agents/telemetry-snapshot.md` `last_finished`
bullet (measured basis, legacy `"unknown"` title) and
`examples/telemetry/snapshot-idle-with-last-finished.json` (`budget_used`
basis `estimated` -> `measured` to match the issue spec).
5. Commit the work on `agent/issue-243`.

## Evidence

- `tests/test_telemetry_last_finished.py` (new, untracked): 14/15 passing;
the single failure was the test's own log-count expectation, corrected to 1
(mirrors `test_malformed_seed_keeps_the_previous_view_and_logs_once`).
- Touched files (uncommitted): `simple_coding_agent/__main__.py`,
`simple_coding_agent/lifecycle.py`, `simple_coding_agent/observability.py`,
`simple_coding_agent/telemetry.py`, `tests/fakes.py`, plus the new test file.
- Gate not yet run: ruff + full pytest suites still pending.
- Code-review gate satisfied: `code-review` invoked with both axes reported.
5 changes: 4 additions & 1 deletion docs/agents/telemetry-snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,10 @@ synthetic examples under `examples/telemetry/` are the conformance target.
`finished_at`, `budget_used`, `turns`, `pull_request: {number, url} |
null`) is held in memory, updated at finalisation, and seeded at startup
from disk, so it survives restarts. It is shown alongside `current_attempt`
too.
too. Its `budget_used` and `turns` figures use the `measured` basis (or
`unavailable` when no token budget was reported); archives written before
the title and pull-request fields existed seed an `"unknown"` title and a
null pull request.
- `recent_events` is the last up to 50 redacted log events, newest last, each
`{timestamp, level, event, issue_number, phase, text}` with `text` truncated
to 300 characters. High-volume `limits_checked` and `tool_call` events are
Expand Down
2 changes: 1 addition & 1 deletion examples/telemetry/snapshot-idle-with-last-finished.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
"finished_at": "2026-09-29T15:40:00Z",
"budget_used": {
"value": 812345,
"basis": "estimated"
"basis": "measured"
},
"turns": {
"value": 23,
Expand Down
1 change: 1 addition & 0 deletions simple_coding_agent/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ def _attempt_sink(issue_number: int) -> AttemptArchive | None:
control_store=ControlStore(config.data_dir),
repository=config.target_repo,
telemetry=telemetry,
data_dir=config.data_dir,
)
interruption_handler = AttemptInterruptionHandler(
event_log=lambda event, detail="", level="INFO", issue_number=None: logger.emit(
Expand Down
Loading
Loading