@agimon-ai/po-profile 0.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +89 -0
- package/dist/apiContracts.cjs +17 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +13 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/mcp.cjs +26 -0
- package/dist/extensions/mcp.cjs.map +1 -0
- package/dist/extensions/mcp.d.mts +5 -0
- package/dist/extensions/mcp.d.mts.map +1 -0
- package/dist/extensions/mcp.mjs +21 -0
- package/dist/extensions/mcp.mjs.map +1 -0
- package/dist/extensions/server.cjs +207 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +179 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +25 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +12 -0
- package/dist/index.d.mts +80 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/profileText-B1dz_9lS.mjs +227 -0
- package/dist/profileText-B1dz_9lS.mjs.map +1 -0
- package/dist/profileText-BkeG0Ulo.cjs +292 -0
- package/dist/profileText-BkeG0Ulo.cjs.map +1 -0
- package/llms.txt +12 -0
- package/package.json +135 -0
- package/src/prompts/po-author-profile/SKILL.md +20 -0
- package/src/prompts/po-author-profile/references/profiles-contract.md +203 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Po profiles authoring contract
|
|
2
|
+
|
|
3
|
+
## Sources and shape
|
|
4
|
+
|
|
5
|
+
Po reads profile declarations in this order:
|
|
6
|
+
|
|
7
|
+
1. `~/.po/profiles.yaml`
|
|
8
|
+
2. `<repoRoot>/.po/profiles.yaml`
|
|
9
|
+
|
|
10
|
+
The current catalog shape is:
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
profiles:
|
|
14
|
+
roots: [agents/acme]
|
|
15
|
+
entries:
|
|
16
|
+
writer:
|
|
17
|
+
persona: agents/special/writer
|
|
18
|
+
env:
|
|
19
|
+
BRAND: acme
|
|
20
|
+
TONE: concise
|
|
21
|
+
defaultProfile: writer
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The document may contain only the `profiles` mapping. In catalog form, `profiles` may contain only
|
|
25
|
+
`roots`, `entries` and `defaultProfile`. Po still accepts the earlier flat form, where profile
|
|
26
|
+
names appear directly below `profiles`, but new configuration should use catalog form.
|
|
27
|
+
|
|
28
|
+
`defaultProfile` names the persona a run starts on when nothing else names one, the same way
|
|
29
|
+
`modes.yaml` declares `defaultMajorMode` and `domains.yaml` declares `defaultDomains`. A repository
|
|
30
|
+
declaration replaces a personal one. `po --profile <name>` overrides it for one run, and an
|
|
31
|
+
inherited `PO_PROFILE` wins so a nested run stays on the persona its parent chose. A
|
|
32
|
+
`defaultProfile` naming a profile that does not exist fails the launch rather than starting with no
|
|
33
|
+
persona.
|
|
34
|
+
|
|
35
|
+
Every relative root or persona path resolves against the file that declares it. Repository paths
|
|
36
|
+
resolve from the repository root. Personal paths resolve from `~/.po`.
|
|
37
|
+
|
|
38
|
+
## Root discovery
|
|
39
|
+
|
|
40
|
+
Each `profiles.roots` value must resolve to an existing directory.
|
|
41
|
+
|
|
42
|
+
- If the root directly contains `profile.md`, `SOUL.md`, or `AGENTS.md`, the root is one profile.
|
|
43
|
+
Its directory name is the profile name, and its children are not scanned.
|
|
44
|
+
- Otherwise, Po inspects only the root's direct-child directories. A child becomes a profile
|
|
45
|
+
when it directly contains at least one persona file.
|
|
46
|
+
- Discovery does not recurse. Add another root or an explicit entry for a deeper persona.
|
|
47
|
+
- A discovered profile has an empty environment mapping.
|
|
48
|
+
- Unreadable candidates and candidates whose real path or persona files escape the configured root
|
|
49
|
+
are ignored. An invalid configured root itself is an error.
|
|
50
|
+
|
|
51
|
+
## Explicit entries and path confinement
|
|
52
|
+
|
|
53
|
+
An explicit entry may contain only `persona`, `env`, and optional `integration`. `persona` is required, must be a non-empty
|
|
54
|
+
relative path, and must resolve to a directory containing readable persona content.
|
|
55
|
+
|
|
56
|
+
The persona must remain inside either:
|
|
57
|
+
|
|
58
|
+
- the `agents/` directory beside the declaring repository root or personal `.po` directory, or
|
|
59
|
+
- one of the `profiles.roots` directories declared in that same file.
|
|
60
|
+
|
|
61
|
+
Po checks both the lexical path and the real filesystem path. A persona-directory symlink cannot
|
|
62
|
+
escape an allowed root. Each resolved persona file must also remain inside the real persona
|
|
63
|
+
directory, so a file symlink cannot import instructions from outside it. Invalid explicit entries
|
|
64
|
+
fail loading instead of being skipped.
|
|
65
|
+
|
|
66
|
+
Persona content is assembled from non-empty files in this order:
|
|
67
|
+
|
|
68
|
+
1. `profile.md`
|
|
69
|
+
2. `SOUL.md`
|
|
70
|
+
3. `AGENTS.md`
|
|
71
|
+
|
|
72
|
+
## Nonsecret integration bindings
|
|
73
|
+
|
|
74
|
+
An explicit profile may bind one integration provider. The binding contains identifiers only,
|
|
75
|
+
never API keys, passwords, cookies, or resolved credential values:
|
|
76
|
+
|
|
77
|
+
```yaml
|
|
78
|
+
profiles:
|
|
79
|
+
entries:
|
|
80
|
+
worker:
|
|
81
|
+
persona: agents/worker
|
|
82
|
+
integration:
|
|
83
|
+
provider: unsendbox
|
|
84
|
+
organizationId: org-id
|
|
85
|
+
mailboxId: mailbox-id
|
|
86
|
+
credentialRef: local-auth-reference
|
|
87
|
+
secrets:
|
|
88
|
+
PASSWORD:
|
|
89
|
+
credentialId: existing-credential-id
|
|
90
|
+
field: password
|
|
91
|
+
allowedOrigins: [https://login.example.com]
|
|
92
|
+
browser:
|
|
93
|
+
profileName: worker
|
|
94
|
+
allowedOrigins: [https://login.example.com]
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`provider`, `organizationId`, `mailboxId`, `credentialRef`, and `secrets` are required.
|
|
98
|
+
`credentialRef` identifies authentication supplied by the concrete integration outside profile
|
|
99
|
+
configuration. An empty `secrets` mapping is allowed. Each uppercase secret alias names an existing
|
|
100
|
+
credential and field; `${secrets.PASSWORD}` is consumed by the bound browser, not profile `env`.
|
|
101
|
+
Each secret requires nonempty exact HTTP(S) `allowedOrigins`, without paths, wildcards, userinfo, or
|
|
102
|
+
trailing slashes. The optional browser binding requires a safe profile name, not a filesystem path,
|
|
103
|
+
and its own nonempty `allowedOrigins` containing every secret origin. Unknown metadata is rejected.
|
|
104
|
+
|
|
105
|
+
The public `@agimon-ai/po-profile` API exposes the session-owned `po/profile-runtime` Cordis service.
|
|
106
|
+
Consumers declare that service as an injected dependency, use `requireProfileRuntime(context)`,
|
|
107
|
+
and attach exactly one concrete provider with `attachProvider(providerName, factory)`. The factory
|
|
108
|
+
receives deeply immutable binding metadata plus `{profileName, generation, signal}` and returns
|
|
109
|
+
`{resolveSecret(reference, signal), dispose()}`. The attachment returns an awaited cleanup operation.
|
|
110
|
+
Do not invoke `runtime.resolveSecret` from the factory: resolution is rejected until initialization
|
|
111
|
+
finishes. The runtime does not load provider SDKs or persist resolved values.
|
|
112
|
+
|
|
113
|
+
`runtime.resolveSecret(name, {origin, signal})` accepts a trusted actual target-frame origin from
|
|
114
|
+
the browser boundary, never a tool-supplied origin. `ready()` rejects if a configured provider is
|
|
115
|
+
missing or failed. Session startup and new-turn admission both require readiness. Profile changes
|
|
116
|
+
and shutdown abort old generations before asynchronous provider cleanup; late secret results are
|
|
117
|
+
rejected rather than delivered to a replacement profile. Persona-only profiles remain compatible.
|
|
118
|
+
The concrete browser integration owns persistence and exclusive profile leases.
|
|
119
|
+
|
|
120
|
+
## Persona identity front-matter
|
|
121
|
+
|
|
122
|
+
A persona may open `profile.md` with a YAML front-matter block declaring who it is and how it
|
|
123
|
+
sounds. Only `profile.md` is read this way; `SOUL.md` and `AGENTS.md` are always prose.
|
|
124
|
+
|
|
125
|
+
```markdown
|
|
126
|
+
---
|
|
127
|
+
name: Rhea
|
|
128
|
+
icon: avatar.png
|
|
129
|
+
voice:
|
|
130
|
+
voice: Karen
|
|
131
|
+
rate: 175
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
You are Rhea, a blunt systems architect.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
All three fields are optional. `name` replaces the profile's directory name in the cockpit,
|
|
138
|
+
`icon` is an image beside `profile.md`, and `voice` shadows `voice.autoCapture.tts` from
|
|
139
|
+
`config.yaml` per field: a persona setting only `rate` keeps the configured voice. The TTS `engine`
|
|
140
|
+
is never overridable, because it selects the synthesiser rather than the persona.
|
|
141
|
+
|
|
142
|
+
Because identity lives here rather than in `profiles.yaml`, it applies to profiles found by root
|
|
143
|
+
discovery as well as to explicit entries.
|
|
144
|
+
|
|
145
|
+
A block is only treated as identity when it is fenced on the very first line, closed, parses as a
|
|
146
|
+
YAML mapping, and declares at least one of `name`, `icon`, or `voice`. Anything else is left in the
|
|
147
|
+
persona text byte for byte, so a persona that legitimately opens with a horizontal rule is
|
|
148
|
+
unaffected. A recognised block is removed from the prompt: it describes the persona rather than
|
|
149
|
+
instructing it. A `profile.md` holding nothing but front-matter therefore has no persona text, and
|
|
150
|
+
the profile fails to load unless `SOUL.md` or `AGENTS.md` supplies it.
|
|
151
|
+
|
|
152
|
+
The icon is read once when the profile loads and travels to the cockpit as a `data:` URL, so no
|
|
153
|
+
file is served at request time. It must be a PNG, JPEG, GIF, or WebP, checked by content rather
|
|
154
|
+
than by file extension. SVG is refused because an SVG rendered in the cockpit executes script. The
|
|
155
|
+
icon must stay inside the persona directory under the same real-path rule as persona files, so a
|
|
156
|
+
symlink cannot smuggle a file out of it. An icon that is missing, unreadable, of another type, or
|
|
157
|
+
larger than 256 KiB once encoded is dropped: the persona keeps its name and the cockpit falls back
|
|
158
|
+
to the default avatar rather than failing the profile.
|
|
159
|
+
|
|
160
|
+
## Precedence
|
|
161
|
+
|
|
162
|
+
Po resolves names in four passes, from lowest to highest precedence:
|
|
163
|
+
|
|
164
|
+
1. Personal root discoveries
|
|
165
|
+
2. Repository root discoveries
|
|
166
|
+
3. Personal explicit entries
|
|
167
|
+
4. Repository explicit entries
|
|
168
|
+
|
|
169
|
+
A later profile replaces an earlier profile with the same name. This means any explicit entry
|
|
170
|
+
overrides discovery, including a personal explicit entry replacing a repository discovery. A
|
|
171
|
+
repository explicit entry wins over a personal explicit entry. Unrelated names from all sources
|
|
172
|
+
remain available, and the final list is sorted by profile name.
|
|
173
|
+
|
|
174
|
+
## Environment defaults and activation
|
|
175
|
+
|
|
176
|
+
Every `env` key maps to a nonsecret string. Secret alias expressions are rejected in `env`. Applying a profile writes only variables that are not already
|
|
177
|
+
exported by the caller. When switching profiles, Po removes a value contributed by the previous
|
|
178
|
+
profile only if that value has not since changed, then applies the next profile's defaults.
|
|
179
|
+
|
|
180
|
+
The `/profile` command resolves the named profile and executes the change through the session
|
|
181
|
+
transition coordinator. A profile change uses Pi reload so extensions re-read the environment.
|
|
182
|
+
Po assembles the persona into a private run-directory file, and the persona Pi entry appends
|
|
183
|
+
that content to the system prompt after reload.
|
|
184
|
+
|
|
185
|
+
Autonomous voice can switch profiles too. The `profile` voice capability lists the configured
|
|
186
|
+
personas and queues a switch, which arrives as `/profile --voice-switch-token=…`. The chosen name
|
|
187
|
+
travels in a TTL-bounded handoff rather than in the command text, so an edited transcript cannot be
|
|
188
|
+
turned into a switch to a different persona. A spoken switch therefore takes the same reload path
|
|
189
|
+
as the command, which is also what lets the new persona's voice take effect: the spoken voice is
|
|
190
|
+
read when autonomous voice activates.
|
|
191
|
+
|
|
192
|
+
## Verification
|
|
193
|
+
|
|
194
|
+
```sh
|
|
195
|
+
pnpm nx test @agimon-ai/po-config
|
|
196
|
+
pnpm nx typecheck @agimon-ai/po-config
|
|
197
|
+
pnpm nx test @agimon-ai/po-profile
|
|
198
|
+
pnpm nx typecheck @agimon-ai/po-profile
|
|
199
|
+
pnpm --filter @agimon-ai/po exec po --profile writer --explain
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Use an actual parent session to smoke-test `/profile` when changing transition, environment, or
|
|
203
|
+
persona application behavior.
|