Skip to content

feat: the agent's identity and soul are two schema-checked documents - #303

Open
eastriverlee wants to merge 3 commits into
mainfrom
feat/identity-and-soul-documents
Open

feat: the agent's identity and soul are two schema-checked documents#303
eastriverlee wants to merge 3 commits into
mainfrom
feat/identity-and-soul-documents

Conversation

@eastriverlee

@eastriverlee eastriverlee commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

What to look at

  • internal/persona/schema/identity.schema.json, soul.schema.json, and user.schema.json: the three documents' shape. The identity carries names[] (every one the agent's own, the first is how it introduces itself) and an optional handle; a person's document carries callMe, about, preferences, tone, language. additionalProperties: false, ceilings on every list and string, schemaVersion pinned to 1. These are the contract the host copies; a drift test on the host side compares its copy with these files.
  • internal/persona/persona.go: parse, normalize, canonical form. render.go: the instruction block the prompt gets, and the AgentIdentity the turn briefing introduces itself with.
  • internal/agentruntime/requester_persona.go: at every launch the requester's private/people/<personID>/.internkim/user.json is read as that person through the workspace actor, rendered, and appended to the turn's host instruction, for that person only. The home is mode 0700, so the service never opens it itself; a refused document is logged as agentruntime.requester_persona_rejected.
  • internal/httpserver/persona_handler.go: GET and PUT /admin/api/persona/user?personID= read and write that file the same way for a host that edits it on the person's behalf; a refused document answers 400 naming the field and writes nothing.
  • internal/app/application.go: identity.json and soul.json replace BOT_PROFILE.yaml, IDENTITY.md, and SOUL.md; a refused document is logged as application.persona_document_rejected and left out.

What the model now sees

Instead of a YAML dump and two pasted markdown files, one block per document, for example:

Identity:
- Your name is 김인턴 (Intern Kim).
- People mention you as @internkim.
- People also write your name as "인턴킴", "intern kim".
- Your role: 회사의 AI 인턴
- Your emoji: 🐱
- When asked who you are, introduce yourself like this: …

What you hold to:
- …
What you never do:
- …
Tone: keep a polite register; sound warm, brief.
Language: write in ko unless something else decides it; answer in the language the requester wrote in.

Under two hundred tokens with every field filled; the ceilings keep it there. A person's document adds one more block, The person asking has told you how they want to work with you:, of at most eight sentences plus a tone and a language line.

Evidence

internal/persona tests cover normalization, rendering, refusal of unknown fields, wrong versions, over-long values, duplicate list items, and a stable canonical round trip. internal/app tests cover the prompt, the identity, and a refused document staying out with its reason recorded. go test ./... passes.

🤖 Generated with Claude Code

@eastriverlee
eastriverlee force-pushed the feat/identity-and-soul-documents branch from 3d1209c to 1240ce7 Compare September 3, 2026 07:17
eastriverlee and others added 2 commits September 3, 2026 18:15
The runtime read three loose files for who the agent is: a YAML profile
the host wrote, plus free-text IDENTITY.md and SOUL.md pasted into the
prompt as they were. Nothing said what a field meant, a typo in a key
vanished silently, and the same fact could sit in all three.

`identity.json` and `soul.json` replace them. Each has a JSON Schema in
`internal/persona/schema/` with `additionalProperties: false`, item and
length ceilings, and a pinned `schemaVersion`; the package parses,
normalizes (trimmed, whitespace-collapsed, deduplicated lists), renders
a deterministic instruction block, and writes a canonical form so the
host and the runtime agree byte for byte. A document the schema refuses
is reported as `application.persona_document_rejected` and left out of
the prompt rather than rendered by guesswork. The agent identity the
turn briefing introduces itself with comes from the same file.

The schema names no product rule such as a fixed name; the host that
writes the file owns that. The hand-rolled YAML reader goes with the
profile it read.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… document

`identity.json` carried one name and an English name, which made every
other spelling an alias of lesser standing. It now carries `names`, every
one of which is the agent's own; the first is how it introduces itself.
The handle leaves the required set because a messenger such as Buzz has
none.

A third document, `.blueclaw/persona/users/<personID>.json`, says how one
person wants to be worked with: what to call them, what to know about
them, their preferences, a tone, a language. The launcher reads the
requester's document at every launch and appends its rendering to the
turn's host instruction, so it reaches the model for that person only.
A person ID that is not a plain file name is refused, and a document the
schema refuses is logged as `agentruntime.requester_persona_rejected`
and left out.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@eastriverlee
eastriverlee force-pushed the feat/identity-and-soul-documents branch from 33cf94a to fe5248a Compare September 3, 2026 09:15
…s them

The person's document sat under the service-owned `.blueclaw/persona/`,
where the host had to write it and the service could read it without
asking. That is the wrong owner: what a person says about themselves is
theirs, and their home is the place the POSIX boundary already protects.

It now lives at `private/people/<personID>/.internkim/user.json`. The
launcher reads it through the workspace actor as the requester, the same
helper path every file tool takes, so a home that is mode 0700 stays
closed to the service. `GET` and `PUT /admin/api/persona/user?personID=`
read and write it the same way for a host that edits it on the person's
behalf; a document the schema refuses answers 400 with the field named
and writes nothing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant