# Project export: PaperTrail

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: PaperTrail is an AI research assistant that uses deep memory architectures to learn and adapt to your interests, reading style, and goals to curate personalized academic paper recommendations.
- Devpost: https://devpost.com/software/papertrail-ydwm46
- GitHub: https://github.com/thorchh/CalHacks-PaperTrail
- Video: https://www.youtube.com/embed/CLcbFt-lo4I?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Letta: Honorable Mention)
- Team: 1 GitHub contributor(s) — Thor Christoffersen Hochman (16 commits)

## Devpost submission (written by the team)

### Inspiration

As a researcher, I find literature review tedious and impersonal. Generic search yields piles of irrelevant results. What if an AI agent learns how you learn, captures your interests and dislikes, reads papers in parallel, and surfaces what matters? I built PaperTrail to bring deep personalization to research discovery.

### What it does

PaperTrail uses multi-agent orchestration with persistent memory. Describe a research topic and it: Searches the papers Scores paper relevance against your interests, goals, expertise, and feedback Returns relevant, personalized recommendations Learns from feedback to refine future suggestions and become more personal Reads specific papers on demand and answers questions in real time Sleeptime compute generates insights over previous conversations, further personalizing the agent. All personalized through a memory layer that adapts to your workflow. How I built it Multi-agent orchestration (Letta): supervisor agent generates targeted queries; workers fetch papers via Jina AI and evaluate in parallel. Deep memory architecture (Letta): profiles store interests, dislikes, goals, expertise, explanation style; conversation history; and feedback tables linking votes to preferences. Personalized scoring: workers use memory blocks to align recommendations to your profile. Real-time learning: each vote updates the user model, shaping future recommendations. Challenges Latency: parallel worker evaluations enabled fast, accurate recommendations. Personalization: memory blocks encoding interests, dislikes, expertise, and style lifted precision. Threading fine-grained preferences into agent prompts reliably. Accomplishments Deep personalization with feedback loops. Smooth real-time paper reading and Q&A. Clear UX that hides multi-agent complexity. Results improve with usage. What I learned Modern LLM orchestration frameworks are powerful. Memory matters: persistent user context beats session-only approaches for quality. Multi-agent systems call for careful supervisor–worker coordination and state handling.

### What's next

Broader coverage: expand beyond arXiv (Google Scholar, semantic scholar). Fine-tuning with user feedback data. Collaboration features to share recommendations. Read-paper sessions with collaborative learning and notes. Big picture: personalized research that learns with you.

## README (from the GitHub repository)

No README available.

## Detected evidence (automated analysis)

Indexed codebase: 93 recognized source files, 522 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Supabase (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- PostgreSQL (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (98 of 98)

```
papertrail/.gitignore
papertrail/app/api/agent/learned/route.ts
papertrail/app/api/chat/route.ts
papertrail/app/api/conversations/[id]/messages/route.ts
papertrail/app/api/conversations/[id]/route.ts
papertrail/app/api/conversations/route.ts
papertrail/app/api/paper-feedback/route.ts
papertrail/app/api/profile/agent-exists/route.ts
papertrail/app/api/profile/create-agent/route.ts
papertrail/app/api/profile/prefs/route.ts
papertrail/app/api/profile/route.ts
papertrail/app/api/profiles/route.ts
papertrail/app/api/reading-queue/route.ts
papertrail/app/api/recommendations/recent/route.ts
papertrail/app/auth/confirm/route.ts
papertrail/app/auth/error/page.tsx
papertrail/app/auth/forgot-password/page.tsx
papertrail/app/auth/login/page.tsx
papertrail/app/auth/sign-up-success/page.tsx
papertrail/app/auth/sign-up/page.tsx
papertrail/app/auth/update-password/page.tsx
papertrail/app/globals.css
papertrail/app/layout.tsx
papertrail/app/page.tsx
papertrail/app/protected/layout.tsx
papertrail/app/protected/page.tsx
papertrail/app/workspace/customize/page.tsx
papertrail/app/workspace/onboarding/page.tsx
papertrail/app/workspace/page.tsx
papertrail/app/workspace/settings/page.tsx
papertrail/components.json
papertrail/components/auth-button.tsx
papertrail/components/chat-input.tsx
papertrail/components/citation-graph-modal.tsx
papertrail/components/dashboard.tsx
papertrail/components/deploy-button.tsx
papertrail/components/env-var-warning.tsx
papertrail/components/feed.tsx
papertrail/components/forgot-password-form.tsx
papertrail/components/hero.tsx
papertrail/components/login-form.tsx
papertrail/components/logout-button.tsx
papertrail/components/main-feed.tsx
papertrail/components/next-logo.tsx
papertrail/components/paper-detail-modal.tsx
papertrail/components/reading-queue.tsx
papertrail/components/right-panel.tsx
papertrail/components/search-modal.tsx
papertrail/components/sidebar.tsx
papertrail/components/sign-up-form.tsx
papertrail/components/supabase-logo.tsx
papertrail/components/theme-switcher.tsx
papertrail/components/tutorial/code-block.tsx
papertrail/components/tutorial/connect-supabase-steps.tsx
papertrail/components/tutorial/fetch-data-steps.tsx
papertrail/components/tutorial/sign-up-user-steps.tsx
papertrail/components/tutorial/tutorial-step.tsx
papertrail/components/ui/avatar.tsx
papertrail/components/ui/badge.tsx
papertrail/components/ui/button.tsx
papertrail/components/ui/card.tsx
papertrail/components/ui/checkbox.tsx
papertrail/components/ui/dialog.tsx
papertrail/components/ui/dropdown-menu.tsx
papertrail/components/ui/input.tsx
papertrail/components/ui/label.tsx
papertrail/components/ui/select.tsx
papertrail/components/ui/slider.tsx
papertrail/components/ui/tabs.tsx
papertrail/components/ui/textarea.tsx
papertrail/components/update-password-form.tsx
papertrail/components/user-avatar.tsx
papertrail/components/workspace-theme.tsx
papertrail/db/migrations/20251026_init.sql
papertrail/db/migrations/20251026_onboarding_profile.sql
papertrail/db/migrations/20251026_profiles_prefs.sql
papertrail/db/migrations/20251027_reading_queue.sql
papertrail/db/migrations/20251028_add_agent_id_to_conversations.sql
papertrail/db/migrations/20251028_add_url_to_reading_queue.sql
papertrail/db/migrations/20251028_conversations.sql
papertrail/db/migrations/20251028_paper_feedback.sql
papertrail/eslint.config.mjs
papertrail/hooks/use-agent-name.ts
papertrail/lib/agents/letta.ts
papertrail/lib/db/db.ts
papertrail/lib/db/types.ts
papertrail/lib/db/user-agents.ts
papertrail/lib/supabase/client.ts
papertrail/lib/supabase/middleware.ts
papertrail/lib/supabase/server.ts
papertrail/lib/utils.ts
papertrail/middleware.ts
papertrail/next.config.ts
papertrail/package.json
papertrail/postcss.config.mjs
papertrail/README.md
papertrail/tailwind.config.ts
papertrail/tsconfig.json
```

### Dependencies

- papertrail/package.json: @eslint/eslintrc@^3, @letta-ai/letta-client@latest, @radix-ui/react-avatar@^1.1.10, @radix-ui/react-checkbox@^1.3.1, @radix-ui/react-dialog@^1.1.1, @radix-ui/react-dropdown-menu@^2.1.14, @radix-ui/react-label@^2.1.6, @radix-ui/react-select@^2.1.2, @radix-ui/react-slider@^1.2.1, @radix-ui/react-slot@^1.2.2, @radix-ui/react-tabs@^1.1.2, @supabase/ssr@latest, @supabase/supabase-js@latest, @types/node@^20, @types/react@^19, @types/react-dom@^19, autoprefixer@^10.4.20, class-variance-authority@^0.7.1, clsx@^2.1.1, eslint@^9, eslint-config-next@15.3.1, lucide-react@^0.511.0, next@latest, next-themes@^0.4.6, postcss@^8, react@^19.0.0, react-dom@^19.0.0, tailwind-merge@^3.3.0, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5

### Recent commits (newest first)

- memory
- no duplicate + sleeptime
- fix conversation history, showing summary, markdown,
- fixing
- tool calls, analyze mode, pdf viewer, fix polling issues
- feedback + conversations
- include user profile in calls + show tool calls
- supabase customization, simple personalization and onboarding, create personal agent on onboarding, reading queue, settings page, add to queue
- supabase
- theme customize
- more logs for supervisor-worker
- add in colour themes + onboarding
- fix color + add real letta + printing
- try fix color + replace chat with letta stub
- copy over ui from downloaded v0
- landing page
- simple landing page + avatar
- init with supabase auth

## Key source files (fetched from GitHub, selected and truncated for size)

### papertrail/package.json

```
{
  "private": true,
  "scripts": {
    "dev": "next dev --turbopack",
    "build": "next build",
    "start": "next start",
    "lint": "eslint ."
  },
  "dependencies": {
    "@radix-ui/react-avatar": "^1.1.10",
    "@radix-ui/react-checkbox": "^1.3.1",
    "@radix-ui/react-dialog": "^1.1.1",
    "@radix-ui/react-select": "^2.1.2",
    "@radix-ui/react-slider": "^1.2.1",
    "@radix-ui/react-tabs": "^1.1.2",
    "@radix-ui/react-dropdown-menu": "^2.1.14",
    "@radix-ui/react-label": "^2.1.6",
    "@radix-ui/react-slot": "^1.2.2",
    "@supabase/ssr": "latest",
    "@supabase/supabase-js": "latest",
    "@letta-ai/letta-client": "latest",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "lucide-react": "^0.511.0",
    "next": "latest",
    "next-themes": "^0.4.6",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^3.3.0"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "autoprefixer": "^10.4.20",
    "eslint": "^9",
    "eslint-config-next": "15.3.1",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "tailwindcss-animate": "^1.0.7",
    "typescript": "^5"
  }
}

```

### papertrail/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist } from "next/font/google";
import { Instrument_Serif } from "next/font/google";
import { ThemeProvider } from "next-themes";
import "./globals.css";
import { WorkspaceTheme } from "@/components/workspace-theme";

const defaultUrl = process.env.VERCEL_URL
  ? `https://${process.env.VERCEL_URL}`
  : "http://localhost:3000";

export const metadata: Metadata = {
  metadataBase: new URL(defaultUrl),
  title: "PaperTrail - An agent that learns how you learn",
  description: "Transform your research workflow with intelligent paper discovery, personalized recommendations, and seamless knowledge management.",
};

const geistSans = Geist({
  variable: "--font-geist-sans",
  display: "swap",
  subsets: ["latin"],
});

const instrumentSerif = Instrument_Serif({
  variable: "--font-instrument-serif",
  display: "swap",
  subsets: ["latin"],
  weight: "400",
});

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body className={`${geistSans.variable} ${instrumentSerif.variable} font-sans antialiased`}>
        <ThemeProvider
          attribute="class"
          defaultTheme="system"
          enableSystem
          disableTransitionOnChange
        >
          <WorkspaceTheme />
          {children}
        </ThemeProvider>
      </body>
    </html>
  );
}

```

### papertrail/app/page.tsx

```typescript
import Link from "next/link";
import { createClient } from "@/lib/supabase/server";

export default async function Home() {
  const supabase = await createClient();
  const { data } = await supabase.auth.getUser();
  const isAuthed = !!data.user;

  return (
    <main className="min-h-screen flex flex-col">
      <nav className="border-b border-border/50 h-16 flex items-center px-6 bg-background/80 backdrop-blur-sm">
        <Link href="/" className="font-serif font-semibold text-lg text-foreground">PaperTrail</Link>
        <div className="ml-auto flex items-center gap-4 text-sm">
          {isAuthed ? (
            <Link href="/workspace" className="px-4 py-2 rounded-lg bg-orange-300 text-orange-900 font-medium hover:bg-orange-400 transition-colors">Go to workspace</Link>
          ) : (
            <>
              <Link href="/auth/login" className="px-4 py-2 rounded-lg border border-border hover:bg-muted/50 transition-colors">Sign in</Link>
              <Link href="/auth/sign-up" className="px-4 py-2 rounded-lg bg-orange-300 text-orange-900 font-medium hover:bg-orange-400 transition-colors">Sign up</Link>
            </>
          )}
        </div>
      </nav>
      <section className="flex-1 flex items-center justify-center p-8 bg-gradient-to-br from-background via-background to-muted/20 relative overflow-hidden">
        {/* Paper emojis */}
        <div className="absolute inset-0 pointer-events-none">
          <div className="absolute top-1/4 left-1/4 text-4xl opacity-20">📄</div>
          <div className="absolute top-1/3 right-1/4 text-3xl opacity-15">📃</div>
          <div className="absolute bottom-1/3 left-1/3 text-3xl opacity-20">📄</div>
          <div className="absolute bottom-1/4 right-1/3 text-4xl opacity-15">📃</div>
          <div className="absolute top-1/2 left-1/6 text-2xl opacity-10">📄</div>
          <div className="absolute top-1/2 right-1/6 text-2xl opacity-10">📃</div>
        </div>
        <div className="max-w-4xl text-center relative z-10">
          <div className="text-5xl md:text-7xl lg:text-8xl font-serif font-light leading-tight mb-12">
            <div>An agent that learns</div>
            <div><em className="italic text-orange-400">how you learn</em></div>
          </div>
          <div className="flex flex-col sm:flex-row items-center justify-center gap-4">
            {isAuthed ? (
              <Link 
                href="/workspace" 
                className="px-10 py-4 rounded-xl bg-orange-300 text-orange-900 font-semibold text-lg hover:bg-orange-400 transition-all duration-200 shadow-lg hover:shadow-xl hover:scale-105 border border-orange-200"
              >
                Open workspace
              </Link>
            ) : (
              <>
                <Link 
                  href="/auth/sign-up" 
                  className="px-8 py-4 rounded-lg bg-primary text-primary-foreground font-medium text-lg hover:bg-primary/90 transition-colors shadow-lg hover:shadow-xl"
                >
                  Get started
                </Link>
                <Link 
                  href="/auth/login" 
                  className="px-8 py-4 rounded-lg border border-border font-medium text-lg hover:bg-muted/50 transition-colors"
                >
                  Sign in
                </Link>
              </>
            )}
          </div>
        </div>
      </section>
    </main>
  )
}

```

### papertrail/lib/supabase/server.ts

```typescript
import { createServerClient } from "@supabase/ssr";
import { cookies } from "next/headers";

/**
 * Especially important if using Fluid compute: Don't put this client in a
 * global variable. Always create a new client within each function when using
 * it.
 */
export async function createClient() {
  const cookieStore = await cookies();

  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY!,
    {
      cookies: {
        getAll() {
          return cookieStore.getAll();
        },
        setAll(cookiesToSet) {
          try {
            cookiesToSet.forEach(({ name, value, options }) =>
              cookieStore.set(name, value, options),
            );
          } catch {
            // The `setAll` method was called from a Server Component.
            // This can be ignored if you have middleware refreshing
            // user sessions.
          }
        },
      },
    },
  );
}

```

### papertrail/app/workspace/page.tsx

```typescript
import { redirect } from "next/navigation"

import { createClient } from "@/lib/supabase/server"
import { UserAvatar } from "@/components/user-avatar"
import { Dashboard } from "@/components/dashboard"
import { getProfile } from "@/lib/db/db"

export default async function WorkspacePage() {
  const supabase = await createClient()

  const { data: { user }, error } = await supabase.auth.getUser()
  if (error || !user) {
    redirect("/auth/login")
  }

  // Ensure profile row exists and route new users to customization
  const { data: profile } = await getProfile(user.id)
  if (!profile) {
    // create minimal profile row and redirect to customize
    await supabase.from("profiles").upsert({ id: user.id, full_name: user.user_metadata?.full_name || null })
    redirect("/workspace/onboarding")
  }
  if (!profile.onboarding_complete) {
    redirect("/workspace/onboarding")
  }

  return (
    <div className="min-h-screen flex flex-col">
      <nav className="border-b h-14 flex items-center px-4">
        <h1 className="font-semibold">PaperTrail</h1>
        <div className="ml-auto">
          <UserAvatar user={user} />
        </div>
      </nav>
      <Dashboard />
    </div>
  )
}



```

### papertrail/app/protected/page.tsx

```typescript
import { redirect } from "next/navigation";

import { createClient } from "@/lib/supabase/server";
import { InfoIcon } from "lucide-react";
import { FetchDataSteps } from "@/components/tutorial/fetch-data-steps";

export default async function ProtectedPage() {
  const supabase = await createClient();

  const { data, error } = await supabase.auth.getClaims();
  if (error || !data?.claims) {
    redirect("/auth/login");
  }

  return (
    <div className="flex-1 w-full flex flex-col gap-12">
      <div className="w-full">
        <div className="bg-accent text-sm p-3 px-5 rounded-md text-foreground flex gap-3 items-center">
          <InfoIcon size="16" strokeWidth={2} />
          This is a protected page that you can only see as an authenticated
          user
        </div>
      </div>
      <div className="flex flex-col gap-2 items-start">
        <h2 className="font-bold text-2xl mb-4">Your user details</h2>
        <pre className="text-xs font-mono p-3 rounded border max-h-32 overflow-auto">
          {JSON.stringify(data.claims, null, 2)}
        </pre>
      </div>
      <div>
        <h2 className="font-bold text-2xl mb-4">Next steps</h2>
        <FetchDataSteps />
      </div>
    </div>
  );
}

```

### papertrail/app/protected/layout.tsx

```typescript
import { DeployButton } from "@/components/deploy-button";
import { EnvVarWarning } from "@/components/env-var-warning";
import { AuthButton } from "@/components/auth-button";
import { ThemeSwitcher } from "@/components/theme-switcher";
import { hasEnvVars } from "@/lib/utils";
import Link from "next/link";

export default function ProtectedLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <main className="min-h-screen flex flex-col items-center">
      <div className="flex-1 w-full flex flex-col gap-20 items-center">
        <nav className="w-full flex justify-center border-b border-b-foreground/10 h-16">
          <div className="w-full max-w-5xl flex justify-between items-center p-3 px-5 text-sm">
            <div className="flex gap-5 items-center font-semibold">
              <Link href={"/"}>Next.js Supabase Starter</Link>
              <div className="flex items-center gap-2">
                <DeployButton />
              </div>
            </div>
            {!hasEnvVars ? <EnvVarWarning /> : <AuthButton />}
          </div>
        </nav>
        <div className="flex-1 flex flex-col gap-20 max-w-5xl p-5">
          {children}
        </div>

        <footer className="w-full flex items-center justify-center border-t mx-auto text-center text-xs gap-8 py-16">
          <p>
            Powered by{" "}
            <a
              href="https://supabase.com/?utm_source=create-next-app&utm_medium=template&utm_term=nextjs"
              target="_blank"
              className="font-bold hover:underline"
              rel="noreferrer"
            >
              Supabase
            </a>
          </p>
          <ThemeSwitcher />
        </footer>
      </div>
    </main>
  );
}

```

### papertrail/app/auth/login/page.tsx

```typescript
import { LoginForm } from "@/components/login-form";

export default function Page() {
  return (
    <div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
      <div className="w-full max-w-sm">
        <LoginForm />
      </div>
    </div>
  );
}

```

### papertrail/app/auth/sign-up/page.tsx

```typescript
import { SignUpForm } from "@/components/sign-up-form";

export default function Page() {
  return (
    <div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
      <div className="w-full max-w-sm">
        <SignUpForm />
      </div>
    </div>
  );
}

```

### papertrail/app/auth/forgot-password/page.tsx

```typescript
import { ForgotPasswordForm } from "@/components/forgot-password-form";

export default function Page() {
  return (
    <div className="flex min-h-svh w-full items-center justify-center p-6 md:p-10">
      <div className="w-full max-w-sm">
        <ForgotPasswordForm />
      </div>
    </div>
  );
}

```

[81 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]