Skip to content

Feat: Turn the strava.py chat into a tool-using agent that can search the running log #2

Description

@sahilds1

Background

Today strava.py is a passive chat: it seeds one activity's JSON into the system prompt and calls conversation.prompt() in a loop. The model only ever sees that single activity — it can't reach for anything else, so it can't compare the run to past efforts or recall earlier coaching. The goal here is a shift in kind, not just a feature: make the chat a small agent — a model that decides, mid-conversation, to call a tool and act on the result. The first tool is a search over the athlete's running log (RUNLOG.txt); the tool-calling loop it establishes is the reusable part.

Existing Behavior

  • strava.py — main() builds model.conversation() and calls conversation.prompt(user_input, system=...). No tools, no agentic loop; the model cannot take actions, only answer from seeded context.
  • strava_prompts.py — SYSTEM_PROMPT describes the coach role over the activity JSON; says nothing about tools.
  • RUNLOG.txt — the athlete's running log, read by nothing today.
  • No strava_tools.py, no agent loop, no tests for tool behavior.

Acceptance Criteria

  • The chat runs an agent loop: model.conversation(tools=[...]) + conversation.chain(...), so the model can decide to call a tool and the loop auto-executes it and re-prompts until a final answer.
  • The agent has one tool — search_runlog(query) in strava_tools.py — a keyword search over the running log, backed by pure parse_runlog/search_entries/format_entries helpers.
  • SYSTEM_PROMPT tells the model the tool exists and when to istory claims in what it returns.
  • Adding a second tool is a one-line change (append to the tools=[...] list) — the loop isn't hard-wired to a single tool.
  • --runlog PATH overrides the default log source.
  • test_strava_tools.py covers the tool at the network-free boundary; uv run pytest passes (existing test_strava.py unaffected).
  • Manual check: ask a history-flavored question and confirm ch_runlog rather than answering from the seeded activity alone.

Approach

The core move is conversation.prompt() → conversation.chain(), p tool list — that's what converts passive chat into an agent. Stay on the incumbent llm library: tools are plain Python functions (docstring → schema), so the agent stays a self-contained script with no new dependency and no framework, consistent with the repo convention. Keep the tool itparse_/search_/format_ helpers behind a thin I/O wrapper) so allthe interesting logic is unit-tested without the model. Structure everything so the agent (the loop + tool registry) is the durable abstraction and search_runlog is just its first registered capability.

References

  • llm tools / Python API: https://llm.datasette.io/en/stable/too — model.conversation(tools=[...]) + conversation.chain(prompt)runs the automatic tool loop; docstrings become tool schemas.
  • Future extension if the agent grows many tools / guardrails / https://developers.openai.com/api/docs/guides/agents (noted in astrava.py comment; deferred to keep the tool surface small).
  • Decisions: stay on llm; self-contained strava_tools.py over framework modules; keyword search over semantic.

Risks and Rollback

Low risk — additive, isolated to the strava scripts. The one behavioral change is the loop swap (prompt → chain); a model/plugin that mishandles tool calls could error on a tool round-trip, in which case the chat degrades to a normal answer or an error, not data loss. Agent-design caveat: on the default Chat
Completions plugin the model re-reasons each turn from the visibte does not carry across turns or across a tool hop — so the agent must treat each tool result as self-contained state (format_entries returns full titles + bodies for this reason). Rollback is a clean revert of
strava_tools.py and the strava.py/strava_prompts.py/docs edits;

Screenshots / Recordings

Pending. Capture a transcript showing the agent choosing to call search_runlog (e.g. "how does this compare to my past half marathons?") and uv run pytest output. Note: the test run was interrupted before execution in this session.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions