Skip to content
127 linesCodeBlameRaw
1import { Brain } from "lucide-react";
2import { Link } from "react-router";
3
4import type { Route } from "./+types/memory";
5import { page } from "../../lib/meta";
6import { AddMemory, MemoryList, memoryAction } from "../../components/memory";
7import { ReviewQueue, reviewAction } from "../../components/context";
8import { agents, memoryReview } from "../../lib/services.server";
9import { assertSameOrigin, requireUser, roleIn, unwrap } from "../../lib/session.server";
10import { requireRepo, requireWritableRepo } from "../../lib/access.server";
11
12export function meta({ params, ...args }: Route.MetaArgs) {
13 return page(args, { title: `Memory · ${params.owner}/${params.repo} · g1t` });
14}
15
16export async function loader({ params, context }: Route.LoaderArgs) {
17 // The project's memory is for anyone who can read the repository; the
18 // workspace's (and what waits for review) for its members only.
19 const { viewer, access } = await requireRepo(context, params, "read");
20 const member = !!roleIn(viewer, params.owner);
21 const path = { namespace: params.owner, name: params.repo };
22 const [memories, candidates] = await Promise.all([
23 agents.listMemories(viewer, params.owner, path),
24 // Candidates waiting for review (the context hub); none if the service is not there yet.
25 member ? memoryReview.listCandidates(viewer, params.owner, path).catch(() => null) : null,
26 ]);
27 return {
28 ...unwrap(memories),
29 candidates: candidates?.ok ? candidates.value : [],
30 member,
31 // Adding to, editing and forgetting the project's memory: Write and up.
32 editable: access.can.push,
33 };
34}
35
36export async function action({ params, context, request }: Route.ActionArgs) {
37 assertSameOrigin(request);
38 const user = requireUser(context, request);
39 await requireWritableRepo(context, params);
40 const form = await request.formData();
41 return (
42 (await reviewAction(memoryReview, user, params.owner, form)) ??
43 memoryAction(agents, user, params.owner, { namespace: params.owner, name: params.repo }, form)
44 );
45}
46
47export default function ProjectMemory({ loaderData, params }: Route.ComponentProps) {
48 const { project, workspace, candidates, member, editable } = loaderData;
49 const action = `/${params.owner}/${params.repo}/memory`;
50 return (
51 <div className="max-w-4xl">
52 <h2 className="flex items-center gap-2 text-xl font-semibold tracking-tight">
53 <Brain size={19} className="text-accent" />
54 Memory
55 </h2>
56 <p className="mt-1.5 max-w-2xl text-sm text-muted">
57 What agents and people have learned about this project that the next agent should know: how
58 to build and test it, its conventions, decisions and why, and its traps. Every run of g1t
59 here is given it, pinned first{member ? ", together with the workspace's memory" : ""}. Agents add to it with
60 the remember tool as they work{editable ? "; keep it true by editing or forgetting what no longer holds" : ""}.
61 </p>
62 {!editable && (
63 <p className="mt-2 text-xs text-muted">
64 You can read this project's memory. Adding to it and changing it needs the Write role.
65 </p>
66 )}
67
68 {editable && (
69 <div className="mt-6">
70 <AddMemory scope="project" action={action} placeholder="The tests need TZ=UTC or the date tests fail." />
71 </div>
72 )}
73
74 {candidates.length > 0 && (
75 <section className="mt-8">
76 <div className="flex items-baseline justify-between">
77 <h3 className="text-sm font-medium">Review</h3>
78 <span className="text-xs text-muted">{candidates.length} waiting</span>
79 </div>
80 <p className="mt-1 text-xs text-muted">
81 Learned by agents, from reviews and merges, and from this project's docs. No agent is given one until it is kept.
82 </p>
83 <div className="mt-3">
84 <ReviewQueue candidates={candidates} action={action} empty="" />
85 </div>
86 </section>
87 )}
88
89 <section className="mt-8">
90 <div className="flex items-baseline justify-between">
91 <h3 className="text-sm font-medium">This project</h3>
92 <span className="text-xs text-muted">{project.length} kept</span>
93 </div>
94 <div className="mt-3">
95 <MemoryList
96 memories={project}
97 action={action}
98 editable={editable}
99 empty={editable ? "Nothing yet. Agents add what they learn here as they work, and so can you." : "Nothing yet."}
100 />
101 </div>
102 </section>
103
104 {member && (
105 <section className="mt-10">
106 <div className="flex items-baseline justify-between">
107 <h3 className="text-sm font-medium">From the workspace</h3>
108 <Link to={`/${params.owner}/-/memory`} className="text-xs text-muted hover:text-fg">
109 Manage workspace memory
110 </Link>
111 </div>
112 <p className="mt-1 text-xs text-muted">
113 True across all of {params.owner}'s projects, and given to agents here too.
114 </p>
115 <div className="mt-3">
116 <MemoryList
117 memories={workspace}
118 action={`/${params.owner}/-/memory`}
119 editable={false}
120 empty="The workspace remembers nothing yet."
121 />
122 </div>
123 </section>
124 )}
125 </div>
126 );
127}