Apply an agent profile to a seat: prefill identity, autonomy, skills, and persona

The AI-seats configurator gains a "Start from a profile (AGENTS.md)" picker. Selecting one loads
the org's resolved profile (builtins + authored + installed, one per key) and prefills the agent's
name, monogram, recommended autonomy, and skills (intersected with the org's skill library), and sets
the operating-guide persona — all still editable before saving. A persona textarea is shown and sent
to ConfigureAgent (already persisted + injected into the run as "# Operating guide"). Closes the loop:
upload/install an AGENTS.md → stand up a seat from it in one step.

Frontend only; the persona/ConfigureAgent path is covered by existing tests. Client build green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
soroush.asadi
2026-06-15 04:14:23 +03:30
parent 05346380e9
commit f79dbda8d2
+68 -1
View File
@@ -64,6 +64,21 @@ interface Agent {
skillKeys: string[]
mcpServerIds: string[]
docs: string[]
persona?: string | null
}
interface AgentProfileLite {
id: string
profileKey: string
name: string
monogram?: string | null
recommendedAutonomy: string
skillKeys: string[]
}
interface AgentProfileDetail {
profile: AgentProfileLite
body: string
}
const AUTONOMY = [
@@ -86,6 +101,7 @@ export function SeatsPage() {
const [mcp, setMcp] = useState({ name: '', endpoint: '', headerName: 'Authorization', headerValue: '' })
const [newSeat, setNewSeat] = useState('')
const [selectedSeat, setSelectedSeat] = useState<string | null>(null)
const [profiles, setProfiles] = useState<AgentProfileLite[]>([])
const [agent, setAgent] = useState({
name: '',
monogram: '',
@@ -94,6 +110,7 @@ export function SeatsPage() {
skillKeys: [] as string[],
mcpServerIds: [] as string[],
docs: '',
persona: '',
})
const run = useCallback(async (action: () => Promise<unknown>) => {
@@ -128,6 +145,11 @@ export function SeatsPage() {
const byKey = new Map<string, Skill>()
for (const s of lib) if (!byKey.has(s.skillKey)) byKey.set(s.skillKey, s)
setSkills([...byKey.values()])
// Agent profiles (AGENTS.md): one selectable entry per key (the resolvable winner is first).
const profileList = await api.get<AgentProfileLite[]>(`/api/orgboard/agent-profiles?organizationId=${organizationId}`)
const byProfileKey = new Map<string, AgentProfileLite>()
for (const p of profileList) if (!byProfileKey.has(p.profileKey)) byProfileKey.set(p.profileKey, p)
setProfiles([...byProfileKey.values()])
await loadConfigs()
await loadMcpServers()
})
@@ -217,11 +239,31 @@ export function SeatsPage() {
skillKeys: existing.skillKeys,
mcpServerIds: existing.mcpServerIds ?? [],
docs: existing.docs.join(', '),
persona: existing.persona ?? '',
}
: { name: '', monogram: '', autonomy: 'Gated', apiConfigId: configs[0]?.id ?? '', skillKeys: [], mcpServerIds: [], docs: '' },
: { name: '', monogram: '', autonomy: 'Gated', apiConfigId: configs[0]?.id ?? '', skillKeys: [], mcpServerIds: [], docs: '', persona: '' },
)
})
// Apply an AGENTS.md profile to the seat: prefill identity, autonomy, skills, and the persona
// (operating guide). The user can still tweak everything before saving.
const applyProfile = (key: string) =>
run(async () => {
const versions = await api.get<AgentProfileDetail[]>(`/api/orgboard/agent-profiles/${key}?organizationId=${organizationId}`)
const chosen = versions[0]
if (!chosen) return
const known = new Set(skills.map((s) => s.skillKey))
setAgent((a) => ({
...a,
name: chosen.profile.name,
monogram: chosen.profile.monogram ?? '',
autonomy: chosen.profile.recommendedAutonomy,
skillKeys: chosen.profile.skillKeys.filter((k) => known.has(k)),
persona: chosen.body,
}))
toast.success(`Applied “${chosen.profile.name}”. Review and save.`)
})
const saveAgent = () =>
run(async () => {
if (!selectedSeat) return
@@ -233,6 +275,7 @@ export function SeatsPage() {
skillKeys: agent.skillKeys,
mcpServerIds: agent.mcpServerIds,
docs: agent.docs ? agent.docs.split(',').map((d) => d.trim()).filter(Boolean) : [],
persona: agent.persona.trim() || null,
})
if (teamId) await loadSeats(teamId)
toast.success(`${agent.name || 'Agent'} configured — seat is now AI.`)
@@ -416,6 +459,19 @@ export function SeatsPage() {
</CardHeader>
{selected && (
<CardContent className="flex flex-col gap-4">
{profiles.length > 0 && (
<Field label="Start from a profile (AGENTS.md)">
<Select value="" onValueChange={applyProfile}>
<SelectTrigger className="w-72"><SelectValue placeholder="Apply an agent profile…" /></SelectTrigger>
<SelectContent>
<SelectGroup>
{profiles.map((p) => <SelectItem key={p.profileKey} value={p.profileKey}>{p.name}</SelectItem>)}
</SelectGroup>
</SelectContent>
</Select>
</Field>
)}
<div className="flex flex-wrap items-end gap-3">
<Field label="Name">
<Input value={agent.name} onChange={(e) => setAgent({ ...agent, name: e.target.value })} className="w-40" placeholder="Aria" />
@@ -494,6 +550,17 @@ export function SeatsPage() {
<Input value={agent.docs} onChange={(e) => setAgent({ ...agent, docs: e.target.value })} placeholder="product-docs, house-style" />
</Field>
<div className="flex flex-col gap-2">
<Label>Operating guide (persona)</Label>
<textarea
value={agent.persona}
onChange={(e) => setAgent({ ...agent, persona: e.target.value })}
rows={4}
placeholder="The agent's persona / operating guide — set by a profile, editable here. Injected into the run."
className="w-full resize-y rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-xs outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50"
/>
</div>
<Button onClick={saveAgent} className="self-start">
<Wand2 data-icon="inline-start" />
Save agent