Portfolio Agent is a tool-using GPT-4o concierge that can navigate the site, surface the right project, and hand off to the Q&A Assistant when a question needs grounded, citation-backed answers. Under the hood: OpenAI Agents SDK, a hand-written portfolio map, typed tools, guardrails, and tracing.
Portfolio Agent is the curious concierge of this site. It is a conversational layer on top of the portfolio built with OpenAI's Agents SDK and GPT-4o. Its job is not to be authoritative — that's what the grounded Q&A Assistant is for — but to help a visitor explore, get oriented, and jump to the right page without having to scroll.
Given a question like "show me the AI projects", the agent can call structured tools, read a portfolio map, and return either a direct answer or a clickable suggestion. When the question gets deep (hiring, skills, specific project internals) it offers a handoff to the Q&A Assistant so the answer stays grounded.
Client send
The chat UI posts the user message and session id to /api/portfolio-agent/chat.
Input guardrails
Each message passes through an input guardrail that filters jailbreak attempts, obvious off-topic prompts, and oversized payloads before reaching the model.
Router agent
A lightweight router classifies intent (navigation, project lookup, handoff-worthy question) and either handles the turn itself or routes to a specialist.
Tool use
The agent invokes typed tools (list projects, get portfolio map, search content, suggest navigation). Tool outputs are streamed back into the agent loop.
Output guardrails
Before returning text to the user the output passes through a final guardrail that checks for unsafe content or hallucinated links.
Response
The final message — optionally with a widget payload (cards, navigation suggestions, handoff prompt) — streams to the UI.
The runtime is built on the @openai/agents SDK. Each agent is a typed object with a name, system instructions, a list of tools, guardrails, and an output type. The SDK handles the tool-calling loop, history, and structured outputs — so the app code is focused on what the agent should do, not the LLM plumbing.
| SDK | @openai/agents v0.8 |
|---|---|
| Primary model | gpt-4o |
| Runtime | Next.js API route (edge-compatible) |
| History | Turn-by-turn session state held per session id |
| Instrumentation | withTrace() span per turn |
Tools are the vocabulary the agent uses to interact with the portfolio. They are plain TypeScript functions with Zod schemas — the SDK handles the JSON schema, validation, and error surfacing. Current tools include:
The router is a thin agent whose only job is to decide where a turn should go. It has three outcomes:
Guardrails run before and after the model. They are not a substitute for careful prompting — they are a safety net.
| Input guardrail | Blocks jailbreak attempts, oversized input, obvious spam |
|---|---|
| Output guardrail | Rejects outputs with hallucinated links or unsafe content |
| Secret binding | PORTFOLIO_AGENT_TOOL_SECRET gates internal tool calls |
| Rate limits | Per-session turn budget enforced in the API route |
The agent does not have free-form access to a database. All grounded data comes from three typed sources:
Every agent turn is wrapped in a withTrace() span. Within a trace we emit structured console logs at the key lifecycle hooks: agent start, tool call, tool done, agent completed, guardrail triggered, and errors. This is what shows up in the OpenAI Traces UI during development and in server logs in production.
| Span name | portfolio-agent |
|---|---|
| Log prefix | [portfolio-agent] |
| Tool events | name, char count, session id |
| Error surface | guardrail hits + unhandled errors |
Portfolio Agent is deliberately narrow. It is a navigation and curiosity layer, not a fact-checking one.