Inspiration
Every UMD student lives inside a dozen different portals. Canvas for classes. Terplink for events and student orgs. Handshake for jobs. Testudo's Schedule of Classes for next semester. Individual professor pages for office hours. ResLife and OCH for housing. Springshare for library rooms. RateMyProfessors. eTerp for HR. The result: the easiest semester to plan is the one you don't.
We wanted one agent that holds your whole semester in its head — courses, deadlines, GPA, completed coursework, interests, friends, budget — and actually acts on your behalf across all those siloed systems. One chat. Real actions. Personalized. The Anthropic × Maryland hackathon was the perfect excuse to build it on Claude, and naming it after our own Diamondback terrapin mascot wrote itself.
What it does
Terp-Agent ships as two pieces:
A Unified Campus API — one FastAPI server (53 endpoints) that wraps every UMD service a student touches. Browse with
GET, get personalized recommendations withPOST /<service>/match(returns ranked items withscore,why, andblockers), and take action with verbs likersvp,book,apply,track,draft_email.Testudo — a Claude-powered chat agent that lives in a floating widget on the dashboard. It runs an agentic tool-use loop with 35 wired tools covering Canvas, Terplink, Handshake, TA/RA jobs, scholarships, housing, libraries, friends, professors, and travel.
You can ask things like:
- "What's due this week in CMSC414?" → pulls assignments, ranks by urgency.
- "Plan my weekend" → parallel calls to events, nearby spots, and weekend trips, then synthesizes one plan.
- "Book a McKeldin room at 2pm tomorrow for 2 hours" → parses the library + time, calls
book_room, confirms. - "What's Priya doing Monday?" → looks up the friend, cross-references her Monday classes with events her interests would pull her to.
- "Find me a TA role I'd actually get and draft outreach to that prof" → chains
match_ta(which factors in GPA + completed prereqs) withdraft_professor_emailin a single turn.
Every recommendation cites why it fits — pulled from real overlap with the student's profile, not invented by the LLM.
How we built it
Backend: FastAPI + Pydantic. 11 service routers (canvas, terplink, handshake, jobs, scholarships, housing, library, social, professors, travel, me) plus an agent router that hosts Testudo. The data layer is a tiny in-memory store loaded from JSON seed files — designed to swap for Postgres without changing call sites. Every router has a # TODO: real integration note pointing to the actual upstream API (Canvas LMS REST, CampusGroups iCal for Terplink, Handshake Partner API, LibCal Springshare, Testudo Schedule of Classes, RateMyProfessors).
Frontend: A single-file HTML dashboard (Tailwind CDN, vanilla JS, no build step). Sidebar with 11 panels — each renders its API's /match output with score bars, "why" bullets, and red blocker chips. Action buttons (RSVP, apply, track, save, book, draft-email, join-group) hit the matching POST. The Testudo chat widget is a floating turtle FAB that opens to a chat panel with quick-prompt chips, a markdown renderer, and tool-call action badges so you can see which tool the agent fired.
Agent — two coexisting modes:
- Heuristic (default, no key needed): a regex-based intent router that handles the common cases. Fast, free, deterministic.
- Claude (when
ANTHROPIC_API_KEYis set): a real LLM agent. Runs up to 8 rounds of tool use againstclaude-sonnet-4-5. The 35 tools are hand-curated with rich descriptions; the system prompt biases Testudo toward action ("don't re-ask permission for casual confirmations") and toward honesty when no tool covers the request. If a Claude call ever fails, the endpoint transparently falls back to heuristic.
Conversation memory is held by the UI — the full message history is sent each turn — so Testudo handles follow-ups like "yeah do that one" or "draft to Prof Hicks instead".
Testing: A 13-case smoke suite plus full dispatcher coverage. The Claude path was verified with a mocked Anthropic client simulating a 3-turn conversation with parallel tool calls in turn 1, chained calls in turn 2, and a synthesized final reply in turn 3.
Challenges we ran into
- Tool design is harder than it looks. Our first cut had 50+ tools; Claude got confused by overlap. We trimmed to 35 with disjoint action-verb names and explicit "use this when…" guidance in every description.
- Conversation memory + tool results. Stitching the SDK's content blocks back into the next request as
tool_resultblocks (with the righttool_use_id) requires careful serialization. We wrote a_serialize_assistant_contenthelper to convert SDK objects back to wire-format dicts. - Word-boundary regex traps.
\bhous\bdoesn't matchhousingbecause s→i is word→word, not a boundary. Same trap withprof/profs. We caught these in regression tests. - Realistic dummy data. Building seed data for ~10 services that felt UMD-specific (actual building names, real prof research areas, plausible event tags) so the agent's recommendations are grounded, not generic.
- Graceful degradation. Making the agent demo-ready without an API key while still upgrading cleanly the moment one is added.
Accomplishments we're proud of
- One agent, every campus surface. 35 tools, 53 endpoints, 11 dashboard panels — and it all coheres into a single chat.
- Claude actually takes action. Books rooms, RSVPs events, drafts personalized prof emails, tracks scholarships. Not a chat-with-search-results bot.
- Personalization is grounded. Every recommendation cites why (skill match, friend overlap, completed prereq, deadline urgency) — pulled from real data.
- Drop-in to production. Each router documents its real-integration target; the in-memory store swaps for Postgres without touching call sites.
What we learned
- System prompts > prompt engineering tricks. Telling Testudo "take action without re-asking" and "be honest when no tool covers it" changed the feel of the agent more than any clever template.
- Tool descriptions are the API spec. Claude reads them carefully — vague descriptions produce wrong tool choices. Treating descriptions as first-class docs raised accuracy noticeably.
- Match endpoints beat list endpoints for agents. Returning
{item, score, why, blockers}lets the model summarize concisely and surface objections without re-deriving them. - Build the heuristic first. It forces you to understand the intent surface and gives you a working demo before you spend a dollar on inference.
What's next for Terp-Agent
- Real integrations. Replace the dummy store with Canvas LMS OAuth2, Handshake Partner API, CampusGroups, LibCal Springshare, Testudo Schedule of Classes, RateMyProfessors.
- FERPA-compliant social graph. Per-student opt-in for classmate matching and group recommendations.
- Proactive notifications. Push when a deadline is < 24h, when a relevant scholarship drops, when a friend RSVPs to an event you'd love.
- Voice mode. Walking to class? Ask Testudo what's due tonight without pulling out a laptop.
- Multi-school. The architecture is school-agnostic — point it at Penn State or VT and it works.
Built With
- agent
- agentic-ai
- anthropic
- anthropic-api
- claude
- claude-sonnet
- docker
- fastapi
- html
- javascript
- pydantic
- python
- regex
- tailwindcss
- tool-use
- uvicorn
Log in or sign up for Devpost to join the conversation.