@aeondave/pi-persona 1.17.3 → 1.17.4
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/AGENTS.md +44 -36
- package/CHANGELOG.md +76 -1
- package/README.md +23 -13
- package/docs/ARCHITECTURE.md +97 -37
- package/docs/COLLABORATION.md +110 -0
- package/docs/EXPERIENCE.md +33 -8
- package/docs/README.md +12 -0
- package/docs/REFERENCE.md +65 -14
- package/package.json +12 -6
- package/src/agents/agent.ts +6 -0
- package/src/bridge.ts +25 -8
- package/src/bus/broker/client.ts +119 -21
- package/src/bus/broker/host.ts +123 -8
- package/src/bus/broker/messages.ts +31 -4
- package/src/bus/contact.ts +113 -38
- package/src/bus/peers.ts +15 -0
- package/src/bus/result-reader.ts +88 -0
- package/src/core/brief.ts +14 -4
- package/src/core/capabilities.ts +1 -1
- package/src/core/leg-thinking.ts +57 -0
- package/src/core/ownership.ts +1 -2
- package/src/core/pi-compat.ts +5 -12
- package/src/core/session-runs.ts +1 -1
- package/src/core/types.ts +1 -1
- package/src/engine/adapter.ts +83 -19
- package/src/engine/async.ts +49 -10
- package/src/engine/child.ts +10 -2
- package/src/engine/inproc.ts +87 -46
- package/src/engine/result-grant.ts +62 -0
- package/src/engine/session-tools.ts +6 -0
- package/src/engine/stream.ts +6 -0
- package/src/engine/worktree.ts +17 -7
- package/src/exocom/collaboration-delivery.ts +29 -0
- package/src/exocom/collaboration.ts +148 -0
- package/src/exocom/envelope.ts +43 -4
- package/src/exocom/gate.ts +7 -2
- package/src/exocom/install.ts +45 -17
- package/src/exocom/ledger.ts +58 -8
- package/src/exocom/plane.ts +32 -1
- package/src/exocom/profile.ts +40 -0
- package/src/exocom/registry.ts +5 -0
- package/src/exocom/status.ts +11 -1
- package/src/exocom/write-guard.ts +7 -0
- package/src/extension/broker-host.ts +61 -9
- package/src/extension/engine.ts +20 -21
- package/src/extension/hooks.ts +952 -938
- package/src/extension/identity.ts +41 -75
- package/src/extension/results.ts +136 -0
- package/src/extension/shared.ts +17 -0
- package/src/extension.ts +2963 -2873
- package/src/loader.ts +11 -3
- package/src/orchestration/flow-journal.ts +7 -1
- package/src/orchestration/flow-run.ts +122 -19
- package/src/orchestration/flow.ts +11 -0
- package/src/orchestration/roster.ts +71 -23
- package/src/orchestration/sdk.ts +44 -6
- package/src/orchestration/types.ts +10 -0
- package/src/persona/model-participants.ts +20 -7
- package/src/persona/orchestrate.ts +2 -0
- package/src/persona/persona.ts +1 -1
- package/src/results/archive.ts +281 -0
- package/src/results/record.ts +166 -0
- package/src/results/retrieval.ts +142 -0
- package/src/results/types.ts +128 -0
- package/src/telemetry/contract.ts +1 -1
- package/src/tools/council.ts +40 -23
- package/src/tools/delegate-tool.ts +50 -14
- package/src/tools/delegate.ts +13 -6
- package/src/tools/exocom-collaboration-input.ts +168 -0
- package/src/tools/exocom-collaboration.ts +315 -0
- package/src/tools/exocom-profile.ts +102 -0
- package/src/tools/exocom.ts +20 -3
- package/src/tools/flow.ts +24 -8
- package/src/tools/intercom-tool.ts +98 -14
- package/src/tools/models.ts +15 -0
- package/src/tools/monitor.ts +20 -1
- package/src/tools/read-result.ts +52 -0
- package/src/tools/timer.ts +23 -2
- package/src/ui/agent-overlay.ts +11 -3
- package/src/ui/agent-tree.ts +43 -1
- package/src/ui/literal-tool-result.ts +1 -1
- package/src/ui/model-picker.ts +1 -1
- package/src/ui/orchestration-launch.ts +49 -0
- package/src/ui/session-cards.ts +191 -14
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Supervised workers and independent colleagues
|
|
2
|
+
|
|
3
|
+
Use **Intercom** for workers launched by this session. Use **Exocom** for independent Pi sessions
|
|
4
|
+
that choose to collaborate. An Exocom peer is responsible to its own user; messages and shared tasks
|
|
5
|
+
do not make it a subordinate or grant it permission to act.
|
|
6
|
+
|
|
7
|
+
## Supervising a leg
|
|
8
|
+
|
|
9
|
+
Choose `thinking` independently of `model` in a delegate or roster entry. The runtime preserves an
|
|
10
|
+
explicit provider choice and reports requested and observed thinking separately. A compact tree
|
|
11
|
+
label is only a projection; it does not prove the provider's internal reasoning effort.
|
|
12
|
+
|
|
13
|
+
Use `intercom peek` to inspect a particular uncertainty, `steer` to queue an instruction, and `stop`
|
|
14
|
+
to stop work. A steering receipt proves acceptance, not that the worker acted on it. Interactive
|
|
15
|
+
completions wake the supervisor automatically; end the turn instead of polling or sleeping.
|
|
16
|
+
|
|
17
|
+
`intercom results` lists terminal reports. `intercom result { ref, offset, limit }` reads a bounded
|
|
18
|
+
page after reload. A `result:<UUID>` identifies stored evidence, not a live worker. The receipt says
|
|
19
|
+
whether storage is durable or memory-only and whether retention truncated the original report.
|
|
20
|
+
Paging to the end cannot restore text that exceeded the retention ceiling.
|
|
21
|
+
|
|
22
|
+
An unanswered decision remains unresolved. Timeout, cancellation, disconnection and headless delivery
|
|
23
|
+
never imply approval. Continue only independent work already authorized, or return a partial result.
|
|
24
|
+
|
|
25
|
+
## Sharing an objective without a leader
|
|
26
|
+
|
|
27
|
+
Start participating sessions with `--exocom` in the same workspace, or join an existing scope with
|
|
28
|
+
its code as described in [the user guide](EXPERIENCE.md#work-with-another-pi-through-exocom).
|
|
29
|
+
Use `exocom_list` to find stable qualified targets and self-reported profiles. A profile's capabilities
|
|
30
|
+
and availability are suggestions for choosing a colleague, not verified skills, reservations or tool
|
|
31
|
+
permissions. Missing availability is **unknown**. Profiles disappear when the Exocom plane stops.
|
|
32
|
+
|
|
33
|
+
1. Describe an independent slice with `exocom_task { action: "create", work_key, description,
|
|
34
|
+
write_set }`. An empty write set means read-only work. Creation is an invitation to collaborate,
|
|
35
|
+
not an assignment. Keep tasks within the users' authorized objective.
|
|
36
|
+
2. Inspect tasks using `exocom_task { action: "list", offset, limit, detail }`. Every task carries a
|
|
37
|
+
`task_id`, the immutable creation message ID, alongside its human-selected `work_key`. Before
|
|
38
|
+
taking a write task, use `exocom_task { action: "get", work_key, task_id }` to inspect the complete
|
|
39
|
+
bounded write set; list previews may shorten paths.
|
|
40
|
+
3. Optionally send `exocom_offer { work_key, task_id, target, expiresInMinutes }`. Omitting target
|
|
41
|
+
records a pool invitation for discovery; it does not broadcast a model wake. A targeted offer uses
|
|
42
|
+
the existing signed wake path. Default expiry is one hour, maximum 24 hours. It never creates a
|
|
43
|
+
response obligation; expiration is not acceptance or rejection.
|
|
44
|
+
4. A colleague voluntarily calls `exocom_task { action: "take", work_key, task_id }`. Pickup and the
|
|
45
|
+
declared file claim commit atomically; a second pickup or overlapping claim fails. A stale task ID
|
|
46
|
+
cannot act on a newly created task that reused the same key. Foreign-workspace members may take
|
|
47
|
+
read-only tasks, but cannot create or take write tasks in the joined scope.
|
|
48
|
+
5. The taker posts `progress` with a bounded `note`, then `finish` with `outcome: "done"` or
|
|
49
|
+
`"abandoned"` and a note, always supplying the same `work_key` and `task_id`. The latest progress
|
|
50
|
+
note is retained. Finishing releases the task's claim; it never merges or approves code.
|
|
51
|
+
|
|
52
|
+
Task division does not need leader election. Each task has one taker and its own claim. Peers choose
|
|
53
|
+
separate open tasks; availability and execution remain up to each session. A peer may propose a different split, decline by doing
|
|
54
|
+
nothing with an offer, or discuss a conflict in a postcard. If a decision belongs to a user, bring it
|
|
55
|
+
to that user; there is no automatic arbitration or authority transfer between peers.
|
|
56
|
+
|
|
57
|
+
Use `exocom_ask` only when a peer's answer must gate the next action. Unlike an offer, an ask constrains
|
|
58
|
+
its addressee until `exocom_answer` or `exocom_decline`. Identity metadata and explicit read actions
|
|
59
|
+
remain available. `exocom_wait` arms a nonblocking wake; it does not block the current tool call.
|
|
60
|
+
|
|
61
|
+
## Publishing evidence
|
|
62
|
+
|
|
63
|
+
`exocom_publish { action: "publish", work_key, task_id, title, excerpt }` copies an explicit excerpt
|
|
64
|
+
into the shared ledger. A title is at most 160 characters; an excerpt is at most 4,096 UTF-16 units
|
|
65
|
+
and 8,192 UTF-8 bytes. No tool follows paths or archive references in that text. To share a retained
|
|
66
|
+
leg report, read the needed page through Intercom, select the relevant excerpt, then publish that text.
|
|
67
|
+
|
|
68
|
+
Discover evidence with `exocom_publish { action: "read", work_key, offset, limit }`, then retrieve one
|
|
69
|
+
excerpt with `msg_id`. Publisher identity is resolved from the registry or retained session ID, never
|
|
70
|
+
the envelope's claimed display name. The excerpt remains fenced peer data. Verify its claims before
|
|
71
|
+
using it; a peer's `done` means that peer finished its task, not that checks passed or a reviewer approved.
|
|
72
|
+
|
|
73
|
+
## Failure, retention and controls
|
|
74
|
+
|
|
75
|
+
`exocom_status` and `/exocom` show claims, questions, shared task states and invitations. Task lists
|
|
76
|
+
default to 10 entries (maximum 20); detailed pages default to 5 and allow at most 5. Evidence-title pages are bounded.
|
|
77
|
+
Tool cards collapse long content and expand using Pi's normal keybinding; this never changes the
|
|
78
|
+
stored evidence or the text supplied to the model.
|
|
79
|
+
|
|
80
|
+
Loss of a task owner's registry lease or explicit release marks unfinished work **stale**. It never
|
|
81
|
+
silently reopens the task. Inspect the work and create a newly scoped task if reassignment is needed.
|
|
82
|
+
Clean shutdown releases both write and read-only task ownership. `exocom_release` intentionally
|
|
83
|
+
releases this session's current ownership and outbound asks on a work key.
|
|
84
|
+
|
|
85
|
+
The ledger retains at most 64 tasks, 64 offers and 64 evidence excerpts in a 128 KiB collaboration
|
|
86
|
+
budget, including lifecycle headroom. Terminal/stale tasks remain visible for the existing one-hour
|
|
87
|
+
result retention window; offers remain until one hour after expiry, evidence for one hour after
|
|
88
|
+
publication. Removing a task also removes its related offers/evidence. Live open/taken tasks are not
|
|
89
|
+
evicted to admit new work. Limits fail explicitly; there is no silent ownership loss or global archive
|
|
90
|
+
access. Transactions perform durable cleanup; reads project current expiry without starting workers.
|
|
91
|
+
|
|
92
|
+
## Contract boundaries
|
|
93
|
+
|
|
94
|
+
| Boundary | Required meaning | Enforcement |
|
|
95
|
+
|---|---|---|
|
|
96
|
+
| Intercom vs Exocom | Worker supervision vs independent peers | Existing capability gates, attribution and distinct transports |
|
|
97
|
+
| Delivery vs decision | Accepted/delivered is not answered, acted or approved | Explicit outcomes and unresolved guidance |
|
|
98
|
+
| Task identity | A reused key cannot retarget old intent | Creation `task_id` checked inside the ledger transaction and signed on frames |
|
|
99
|
+
| Pickup and ownership | One taker, claim admitted together, no overlap | Atomic ledger transaction; scope/capability admission inside its lock |
|
|
100
|
+
| Optional offer vs ask | Invitations never gate; targeted asks do | Separate records, tool actions and pending-ask gate |
|
|
101
|
+
| Evidence | Explicit bounded copied text, no cross-session archive access | Schema validation, ledger budgets, registry attribution and fences |
|
|
102
|
+
| Persistence | Versioned authoritative state must not become an empty ledger on error | Snapshot validation, retained-message idempotency and lifecycle budget reservation |
|
|
103
|
+
| Retention | Stale is visible, expiry never implies takeover | First-stale timestamp, bounded retention, fresh generation on recreation |
|
|
104
|
+
| Profiles and views | Self-report/projection cannot grant authority | Separate presence metadata and runtime-owned status |
|
|
105
|
+
| Flow references | Only compatible, successful archived phases can resume | Journal-version/status checks, scoped reads, explicit unavailable/truncated outcomes |
|
|
106
|
+
|
|
107
|
+
These are runtime contracts. The existing `outputContract` facility validates a worker's final JSON
|
|
108
|
+
report with scalar fields; a model-written `proof` is not a command receipt. It is not the right place
|
|
109
|
+
to encode task ownership or ledger transitions. Nested report schemas and host-executed acceptance
|
|
110
|
+
receipts remain separate product work; this collaboration protocol does not claim to implement them.
|
package/docs/EXPERIENCE.md
CHANGED
|
@@ -80,6 +80,10 @@ receipt previews the message that was actually queued; expand it (`Ctrl+O` by de
|
|
|
80
80
|
configured key) to read the full message and diagnostic run ID. Aliases are display names, not
|
|
81
81
|
addresses: tools still route by run ID, and duplicate aliases show a secondary ID to distinguish them.
|
|
82
82
|
Saved launch/control receipts keep their display names even after the runtime tracker is gone.
|
|
83
|
+
Council and flow launch receipts also retain each participant's model and requested effort, with
|
|
84
|
+
phase and auxiliary roles identified. Expand the card for the full panel. A pending model choice
|
|
85
|
+
is marked explicitly; launch selections are not proof of the effective model or effort after
|
|
86
|
+
setup, provider recovery or Pi's effort adjustment. Later settings changes do not rewrite history.
|
|
83
87
|
|
|
84
88
|
A truncated message includes a message id. The supervisor can retrieve its retained body with
|
|
85
89
|
`intercom { action: "message", messageId: "m1" }`. A worker's final report has a run id instead:
|
|
@@ -92,14 +96,35 @@ outcomes that make sense without remembering those names.
|
|
|
92
96
|
|
|
93
97
|
## Work with another Pi through Exocom
|
|
94
98
|
|
|
95
|
-
Use Intercom for this supervisor's workers and Exocom for independent Pi sessions.
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
99
|
+
Use Intercom for this supervisor's workers and Exocom for independent Pi sessions. Use
|
|
100
|
+
`exocom_list` or `/exocom` to find the intended session-qualified target before sending a postcard
|
|
101
|
+
or asking a question.
|
|
102
|
+
|
|
103
|
+
`exocom_task create` records a voluntary objective and declared write paths; it does not start
|
|
104
|
+
another Pi or require a peer to accept. The receipt gives a `task_id`; keep it with the
|
|
105
|
+
`work_key` when inviting, taking, reporting progress, finishing or publishing evidence. A work key
|
|
106
|
+
can be reused after retention, so the task id identifies the exact generation. The peer chooses
|
|
107
|
+
whether to take the task. Write-set pickup and its advisory claim happen atomically, but a claim is
|
|
108
|
+
not an OS file lock. A peer that joined from another workspace can take read-only tasks, not tasks
|
|
109
|
+
that claim paths in the selected workspace. Use `exocom_task get` to inspect the complete write set;
|
|
110
|
+
the paginated list is a summary.
|
|
111
|
+
|
|
112
|
+
Use `exocom_offer` for a non-blocking invitation that expires by itself; it creates no obligation
|
|
113
|
+
to answer. Use `exocom_ask` when a specific decision is needed and the recipient must answer or
|
|
114
|
+
decline. A pending ask temporarily permits settlement, `exocom_name`, `exocom_status`, explicit
|
|
115
|
+
read actions (task list/get, evidence read and profile get), and read-only filesystem tools.
|
|
116
|
+
Changing `exocom_profile` is not allowed while an ask is pending. Changing the name does not answer
|
|
117
|
+
the ask.
|
|
118
|
+
|
|
119
|
+
Share only a bounded excerpt through `exocom_publish`; it does not grant access to another session's
|
|
120
|
+
terminal-result archive. Peer text is untrusted and appears fenced with registry-resolved attribution.
|
|
121
|
+
Capability labels and availability in `exocom_profile` are self-reported metadata, not verified
|
|
122
|
+
skills or permissions. Exchange decisions and evidence when they affect the work; acknowledgement-only
|
|
123
|
+
conversations add noise. If a peer joined from another workspace, agree which workspace owns each
|
|
124
|
+
artifact.
|
|
125
|
+
|
|
126
|
+
See the [cross-session collaboration guide](./COLLABORATION.md) for command shapes, retention limits,
|
|
127
|
+
and detailed examples.
|
|
103
128
|
|
|
104
129
|
`exocom_status` (or `/exocom`) shows who owns what right now: your open claims, a peer's open
|
|
105
130
|
claims, and any asks waiting on either side. Claims are advisory, not a lock — if you write into a
|
package/docs/README.md
CHANGED
|
@@ -16,6 +16,8 @@ introduced a behavior. An `Unreleased` entry is not yet part of a published rele
|
|
|
16
16
|
- Vendor-neutral observer contract, privacy boundary, persistence, and consumer conformance.
|
|
17
17
|
- [`EXPERIENCE.md`](./EXPERIENCE.md)
|
|
18
18
|
- Choosing a persona, supervising background work, and collaborating through Exocom.
|
|
19
|
+
- [`COLLABORATION.md`](./COLLABORATION.md)
|
|
20
|
+
- Intercom supervision, voluntary peer tasks, invitations, evidence, retention and runtime contracts.
|
|
19
21
|
- [`MONITORS.md`](./MONITORS.md)
|
|
20
22
|
- Clock snapshots, timers, and background event sources that wake the supervisor.
|
|
21
23
|
- [`REFERENCE.md`](./REFERENCE.md)
|
|
@@ -28,6 +30,16 @@ introduced a behavior. An `Unreleased` entry is not yet part of a published rele
|
|
|
28
30
|
- **For behavior-layer changes:** consult `SPINE.md` and `src/core` / `src/persona/spine.ts` together.
|
|
29
31
|
- **For telemetry producers or consumers:** start with `TELEMETRY.md` and its conformance corpus.
|
|
30
32
|
|
|
33
|
+
## Research and verification records
|
|
34
|
+
|
|
35
|
+
- [`The pi-subagents comparison`](https://github.com/AeonDave/pi-persona/blob/main/docs/reviews/2026-10-24-pi-subagents.md) records the bounded source
|
|
36
|
+
study and links its evidence and source notes.
|
|
37
|
+
- [`Current-host cleanup and final requalification`](https://github.com/AeonDave/pi-persona/blob/main/docs/reviews/2026-10-25-current-host-cleanup.md)
|
|
38
|
+
is the latest qualification report and links the preceding implementation review for its distinct
|
|
39
|
+
behavioral and live-run evidence.
|
|
40
|
+
- [`ROADMAP.md`](https://github.com/AeonDave/pi-persona/blob/main/ROADMAP.md) tracks the remaining work, including the unqualified
|
|
41
|
+
[compaction proposal](https://github.com/AeonDave/pi-persona/blob/main/docs/plans/2026-10-20-compaction-evaluation-proposal.md).
|
|
42
|
+
|
|
31
43
|
## Documentation maintenance notes
|
|
32
44
|
|
|
33
45
|
When touching this folder:
|
package/docs/REFERENCE.md
CHANGED
|
@@ -34,7 +34,7 @@ hard-fail.
|
|
|
34
34
|
|
|
35
35
|
| Surface | Does |
|
|
36
36
|
|---|---|
|
|
37
|
-
| `delegate` tool | spawn one or more sub-agents; interactive sessions always run it in the background (results return as completion follow-ups); headless `pi -p` always blocks and returns results inline. There is no `sync`/`async` parameter. A leg whose tool is silent longer than the idle window (180 s) fails with a message telling you to raise that leg's `timeoutMs`. A `tasks` fan-out's receipt pairs each run id with its call-sign, and one wake covers the whole batch |
|
|
37
|
+
| `delegate` tool | spawn one or more sub-agents; interactive sessions always run it in the background (results return as completion follow-ups); headless `pi -p` always blocks and returns results inline. There is no `sync`/`async` parameter. A leg whose tool is silent longer than the idle window (180 s) fails with a message telling you to raise that leg's `timeoutMs`. A `tasks` fan-out's receipt pairs each run id with its call-sign, and one wake covers the whole batch. An optional `thinking` picks that leg's effort level (off…max); omission keeps the agent's own setting and the configured/inherited child default, and results distinguish requested from observed effort |
|
|
38
38
|
| `council` tool | select any installed strategy over a named roster or temporary inline `members`; voting strategies preserve tally and dissent. Interactive sessions run a council in the background, like `delegate`: a receipt with a run id now, ONE completion follow-up with the ruling (`intercom result { to: runId }` for the full payload); headless `pi -p` returns it inline. Use it when the method adds value (vote, arbiter's pick, critique loop, per-item sweep, rival builds), not for plain independent parallel work, which is `delegate`. The strategy list in the tool description is generated from the registry (`when`, positional `roster`, `*` for a required param); a call missing a required param (`judge` and `compete` need `params.judge`) fails at once with `council failed: "<strategy>" requires params.<key> — …` and launches nothing, while an unknown strategy or roster is reported by the run itself, in the completion follow-up. A persona's declared `council:` still resolves when strategy, roster and params are omitted. Per-strategy behaviour (section labels, MAGI reflection fallbacks) is in [`STRATEGIES.md`](./STRATEGIES.md) |
|
|
39
39
|
| `intercom` tool | observe and control internal runs with `peek`, `result`, `steer`, and `stop`; `list`, `inbox`, `message`, `reply`, and `send` drive the coaching bus. There is no `wait`: a finished child wakes the supervisor through the completion follow-up |
|
|
40
40
|
| `timer` tool | read the fresh clock with `now`, or schedule an idle wake with `arm`, `cancel`, and `list`; alarms are one-shot and in-memory per session; an armed alarm shows as a row in the agent widget and F9 until it fires or is cancelled |
|
|
@@ -77,14 +77,15 @@ its lighter variants when present. The current provider is a default, not a user
|
|
|
77
77
|
explicit worker-model request may select another configured provider. The LLM retains the choice.
|
|
78
78
|
|
|
79
79
|
The delegate tool's optional `name` is carried into `AgentRunSpec.name` and reaches both engine
|
|
80
|
-
backends, keeping a caller-provided run label available in the live tree and runtime result.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
backends, keeping a caller-provided run label available in the live tree and runtime result. A
|
|
81
|
+
successful or stopped run announces ONCE: the idle-gated completion follow-up is the single
|
|
82
|
+
settlement surface (the agent-tree row already settles immediately). A failure keeps an immediate
|
|
83
|
+
operator surface — in the TUI a durable expandable card with the agent, model, elapsed time and a
|
|
84
|
+
concise cause (expansion retains the full technical diagnostic); other modes use a concise
|
|
85
|
+
sanitized notification. These operator-only cards do not inject context or start a second turn.
|
|
86
|
+
The model-facing report remains independently idle-gated; a UI notification failure cannot discard it.
|
|
86
87
|
|
|
87
|
-
###
|
|
88
|
+
### Native codemode
|
|
88
89
|
|
|
89
90
|
Pi's native `codemode` can call callable pi-persona tools through the normal nested-tool pipeline.
|
|
90
91
|
Argument validation and persona permission gates still apply to every inner call; scripts do not
|
|
@@ -124,13 +125,18 @@ a live pending ask on the exact child handle, never inferred from its last activ
|
|
|
124
125
|
| Surface | Needs | What you get |
|
|
125
126
|
|---|---|---|
|
|
126
127
|
| **Observe and control** | any persona | `peek` inspects async sub-agents; `steer` sends a soft correction; `stop` requests cancellation, and `stop { to: "all" }` cancels every running run, `council` and `flow` included (runs already stopping are skipped, never force-cleared in bulk; a slash or mandated run is stopped at shutdown, not by this). The `f9` overlay provides the same controls (`s`, `x`, `X`). Cancelling a worker does not undo existing changes; an unknown id on `result`, `steer` or `stop` returns a miss text listing the known runs |
|
|
127
|
-
| **Retrieve** | any persona | `result { to }` returns one retained run payload; `message { messageId }` returns one retained bus message. History is bounded to 256 messages and 256,000 body characters, and retrieval never starts a worker |
|
|
128
|
-
| **Message bus** | `coaching: true` | children get `contact_supervisor
|
|
128
|
+
| **Retrieve** | any persona | `result { to }` returns one retained run payload (`offset`/`limit` page a bounded view without collecting it; `collect: true` acknowledges whole-body collection of a live run); `result { ref, offset, limit }` reads a page of a retained terminal result after reload; `results` lists retained terminal results with immutable refs; `message { messageId }` returns one retained bus message. History is bounded to 256 messages and 256,000 body characters, terminal results to 128 entries / 8 MiB per session, and retrieval never starts a worker |
|
|
129
|
+
| **Message bus** | bus allowed; in-process children also require `coaching: true` | broker-connected children and coached in-process children get `contact_supervisor`, subject to explicit empty tool grants and exclusions; progress appears in results and `intercom inbox`, while a blocking `decision` wakes the supervisor for `intercom reply` |
|
|
129
130
|
| **Sibling peer comm** | strategy opt-in | `debate` and `pair` members always get one-way `contact_peer`; `map` and `synthesize` add it with `params: { peers: true }` |
|
|
130
131
|
|
|
131
|
-
|
|
132
|
-
A headless (inline) child posts the question one-way
|
|
133
|
-
|
|
132
|
+
When supervisor contact is available, an async child's `decision` or `interview` waits for a supervisor reply.
|
|
133
|
+
A headless (inline) child posts the question one-way without waiting: the supervisor is parked
|
|
134
|
+
in that call and cannot answer. The decision/interview remains unresolved; the child may continue
|
|
135
|
+
only independent work already authorized, otherwise it returns a blocked/partial result. A timeout,
|
|
136
|
+
disconnection or failure is never permission to invent an answer. Results expose `outcome` as
|
|
137
|
+
`reported`, `answered`, `unavailable`, `timed-out`, `cancelled` or `failed`; `delivered` means only
|
|
138
|
+
confirmed delivery, not a reply or approval (false can mean unconfirmed). An already-cancelled call
|
|
139
|
+
sends nothing. Progress messages remain one-way.
|
|
134
140
|
|
|
135
141
|
Delegated output is untrusted and is fenced before it reaches the supervisor. Interactive
|
|
136
142
|
delegation always runs in the background: continue independent work, and when only children remain, end the turn for
|
|
@@ -200,6 +206,43 @@ and your own open asks. A claim's `write_set` is advisory, not enforced: a super
|
|
|
200
206
|
coordinate (`exocom_ask`) or claim the path itself; calling the same tool again on the same path is
|
|
201
207
|
then allowed through (one warning per claim + normalized path, not a standing lock).
|
|
202
208
|
|
|
209
|
+
### Exocom collaboration tools
|
|
210
|
+
|
|
211
|
+
The external plane exposes fifteen call-time-gated tools: `exocom_list`, `exocom_send`,
|
|
212
|
+
`exocom_name`, `exocom_claim`, `exocom_ask`, `exocom_answer`, `exocom_decline`, `exocom_wait`,
|
|
213
|
+
`exocom_release`, `exocom_progress`, `exocom_status`, `exocom_task`, `exocom_offer`,
|
|
214
|
+
`exocom_publish`, and `exocom_profile`. Persona denials still apply during an active session; a
|
|
215
|
+
foreign-workspace member does not receive usable `exocom_claim` access.
|
|
216
|
+
|
|
217
|
+
| Tool | Contract |
|
|
218
|
+
|---|---|
|
|
219
|
+
| `exocom_list`, `exocom_send`, `exocom_name` | Discover session-qualified targets, send a one-way postcard, or set this session's display handle. A rename does not change the routing identity. |
|
|
220
|
+
| `exocom_claim`, `exocom_release`, `exocom_progress`, `exocom_status` | Declare advisory repository-relative ownership, release it, add progress, or inspect current ledger state. Claims do not enforce OS-level write access. |
|
|
221
|
+
| `exocom_ask`, `exocom_answer`, `exocom_decline`, `exocom_wait` | Send a targeted question that creates an obligation, settle an incoming ask, or arm a non-blocking wake for an answer. |
|
|
222
|
+
| `exocom_task` | Create a voluntary task (creation does not launch a worker), take it, report progress, finish it as `done` or `abandoned`, or inspect retained tasks. Use `list` then `get` for full details. |
|
|
223
|
+
| `exocom_offer` | Send an optional invitation for an open task to a peer or the pool. It expires after one hour by default and at most 24 hours; it never creates an ask or an obligation to answer. |
|
|
224
|
+
| `exocom_publish` | Share an explicit bounded evidence excerpt for a task, discover titles/message ids by `work_key`, and retrieve one excerpt by `msg_id`. This does not expose an `intercom` terminal-result archive. |
|
|
225
|
+
| `exocom_profile` | Read, set or clear this session's self-reported capability labels and availability. Up to eight labels of 48 characters each; availability is `available`, `busy`, `away` or unknown. It never grants permission or verifies skill. |
|
|
226
|
+
|
|
227
|
+
`exocom_task list` is discovery: omit `work_key` and `task_id`, then use the returned identifiers for
|
|
228
|
+
`get` or a task action. Summary pages default to 10 rows; `detail: true` defaults to 5, and an
|
|
229
|
+
explicit detailed limit above 5 is rejected. `exocom_task create` returns `task_id`, which equals
|
|
230
|
+
the creation message id. Carry it with `work_key` for take/progress/finish, offers and evidence
|
|
231
|
+
publication; the key alone is only a lookup name and can be reused after retention. Write-set pickup
|
|
232
|
+
and claim are committed atomically under the ledger lock. Read-only tasks can be taken from another
|
|
233
|
+
workspace scope. A task whose owner leaves becomes stale and cannot be taken over automatically.
|
|
234
|
+
Detailed paths are available through `exocom_task({ action: "get", work_key, task_id })`;
|
|
235
|
+
paginated list summaries may clip the write set.
|
|
236
|
+
|
|
237
|
+
While an incoming ask is pending, allowed actions are answer/decline, `exocom_name`, status, explicit
|
|
238
|
+
collaboration reads (`exocom_task` list/get, `exocom_publish` read, `exocom_profile` get), and
|
|
239
|
+
read-only filesystem tools. Profile edits and other mutations wait until the ask is settled. Identity
|
|
240
|
+
metadata does not settle it. Task, offer and evidence records are each capped at 64 and share a
|
|
241
|
+
128 KiB state budget; terminal or stale tasks and expired offers are retained for up to an hour,
|
|
242
|
+
while evidence is retained for up to an hour after publication. Active tasks remain while their owner
|
|
243
|
+
lease is live. See the [cross-session collaboration guide](./COLLABORATION.md) for workflows and
|
|
244
|
+
bounded examples.
|
|
245
|
+
|
|
203
246
|
## Configuration and data
|
|
204
247
|
|
|
205
248
|
Personas and agents are Markdown files with YAML frontmatter. Teams, flows, contracts, and presets
|
|
@@ -592,6 +635,14 @@ queued or later stages may still start.
|
|
|
592
635
|
`/persona-settings [show]` · `/models [query]` · `/orchestrate <task>` · `/flow <name> <task>` ·
|
|
593
636
|
`/peek [id]` · `/exocom` · `/doctor`.
|
|
594
637
|
|
|
638
|
+
`/persona off` is the deactivation command. `/persona restore` force-restores every bundled resource
|
|
639
|
+
into the user persona directory: personas and agents, flows, contracts, presets, `teams.yaml`, and
|
|
640
|
+
the shared prompt pair. Existing bundled destinations are overwritten; additional user files are
|
|
641
|
+
not pruned. For example, custom `agents/pippo.md` (a persona) and `agents/pluto.md` (an agent) survive
|
|
642
|
+
unchanged. Reusing a bundled destination filename means opting into its replacement. This is not a
|
|
643
|
+
reset of `settings.json`, credentials, sessions or project-local overrides. Definitions are reloaded
|
|
644
|
+
afterward, including any preserved custom persona that was active.
|
|
645
|
+
|
|
595
646
|
`/doctor` reports the cross-process broker's lifecycle on one line: `broker: on — endpoint …` once
|
|
596
647
|
the host is listening, `broker: on — (starting…)` mid-attempt, or
|
|
597
648
|
`broker: on — (not started — no child-engine build yet)` before any child-engine build has run. A
|
|
@@ -602,7 +653,7 @@ build retries. A connected broker child's status shows `⇄ <handle>`; if that c
|
|
|
602
653
|
status flips to `⇄ offline` and a fresh ask on it fails fast instead of hanging.
|
|
603
654
|
|
|
604
655
|
`/doctor` also reports the installed host's version against this extension's floor: `pi: <version>
|
|
605
|
-
(requires ≥ 1.
|
|
656
|
+
(requires ≥ 1.4.0)`, with a trailing `— BELOW FLOOR` marker once an installed version fails the
|
|
606
657
|
check (`src/core/pi-compat.ts`). `pi: unknown` means the host's own `package.json` could not be
|
|
607
658
|
resolved (a non-standard install layout) — not a failed check.
|
|
608
659
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeondave/pi-persona",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.4",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Supervised multi-agent orchestration for Pi — async workers, live steering, cross-session collaboration, and switchable personas.",
|
|
@@ -42,6 +42,7 @@
|
|
|
42
42
|
"docs/README.md",
|
|
43
43
|
"docs/ARCHITECTURE.md",
|
|
44
44
|
"docs/EXPERIENCE.md",
|
|
45
|
+
"docs/COLLABORATION.md",
|
|
45
46
|
"docs/MONITORS.md",
|
|
46
47
|
"docs/REFERENCE.md",
|
|
47
48
|
"docs/SPINE.md",
|
|
@@ -94,18 +95,23 @@
|
|
|
94
95
|
"node": ">=22.19.0"
|
|
95
96
|
},
|
|
96
97
|
"devDependencies": {
|
|
97
|
-
"@earendil-works/pi-agent-core": "1.
|
|
98
|
-
"@earendil-works/pi-ai": "1.
|
|
99
|
-
"@earendil-works/pi-coding-agent": "1.
|
|
100
|
-
"@earendil-works/pi-tui": "1.
|
|
98
|
+
"@earendil-works/pi-agent-core": "1.4.0",
|
|
99
|
+
"@earendil-works/pi-ai": "1.4.0",
|
|
100
|
+
"@earendil-works/pi-coding-agent": "1.4.0",
|
|
101
|
+
"@earendil-works/pi-tui": "1.4.0",
|
|
101
102
|
"@types/node": "^22.20.4",
|
|
102
103
|
"fast-check": "^4.10.2",
|
|
103
104
|
"tsx": "^4.23.13",
|
|
104
105
|
"typebox": "1.4.2",
|
|
105
106
|
"typescript": "^7.0.2"
|
|
106
107
|
},
|
|
108
|
+
"overrides": {
|
|
109
|
+
"@earendil-works/pi-coding-agent@1.4.0": {
|
|
110
|
+
"undici": "8.12.3"
|
|
111
|
+
}
|
|
112
|
+
},
|
|
107
113
|
"pi": {
|
|
108
|
-
"image": "https://raw.githubusercontent.com/AeonDave/pi-persona/v1.17.
|
|
114
|
+
"image": "https://raw.githubusercontent.com/AeonDave/pi-persona/v1.17.4/assets/banner.png",
|
|
109
115
|
"video": "https://cdn.jsdelivr.net/gh/AeonDave/pi-persona@6655d281ce7965a75303dd704bd29db73db04070/assets/demo1.mp4",
|
|
110
116
|
"extensions": [
|
|
111
117
|
"./src/extension.ts"
|
package/src/agents/agent.ts
CHANGED
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
|
|
11
11
|
import { sanitizeStatusLabel } from "../core/display-label.ts";
|
|
12
12
|
import { asBoolean, asPermission, parseYamlSubset, splitFrontmatter } from "../core/frontmatter.ts";
|
|
13
|
+
import { parseLegThinking } from "../core/leg-thinking.ts";
|
|
14
|
+
import type { ThinkingLevel } from "../core/types.ts";
|
|
13
15
|
import { writeSetPathError } from "../core/ownership.ts";
|
|
14
16
|
|
|
15
17
|
export interface AgentConfig {
|
|
@@ -21,6 +23,8 @@ export interface AgentConfig {
|
|
|
21
23
|
* prompt text: the body already carries the behaviour, this only labels it. */
|
|
22
24
|
purpose?: string;
|
|
23
25
|
model?: string;
|
|
26
|
+
/** Default requested effort for this agent; an explicit leg override wins. Pi clamps it. */
|
|
27
|
+
thinking?: ThinkingLevel;
|
|
24
28
|
/** Tool allowlist passed to Pi. Absent means the session default tool set. */
|
|
25
29
|
tools?: string[];
|
|
26
30
|
/** Tool denylist applied after the allowlist/session default. */
|
|
@@ -73,6 +77,8 @@ export function parseAgent(content: string, source: string): AgentConfig | null
|
|
|
73
77
|
if (purpose && purpose !== "agent") agent.purpose = purpose;
|
|
74
78
|
}
|
|
75
79
|
if (typeof fm.model === "string" && fm.model.trim()) agent.model = fm.model.trim();
|
|
80
|
+
const thinking = parseLegThinking(fm.thinking);
|
|
81
|
+
if (thinking !== undefined) agent.thinking = thinking;
|
|
76
82
|
const tools = asPermission(fm.tools);
|
|
77
83
|
if (tools?.allow) agent.tools = tools.allow;
|
|
78
84
|
if (tools?.deny) agent.excludeTools = tools.deny;
|
package/src/bridge.ts
CHANGED
|
@@ -23,6 +23,9 @@ import { makeContactSupervisorTool } from "./bus/contact.ts";
|
|
|
23
23
|
import { InProcessBus, type MsgKind } from "./bus/inproc.ts";
|
|
24
24
|
import { makeContactPeerTool, type PeerInfo } from "./bus/peers.ts";
|
|
25
25
|
import { attributeInbound } from "./core/fence.ts";
|
|
26
|
+
import { isReaderToken } from "./bus/broker/messages.ts";
|
|
27
|
+
import { readFailure } from "./bus/result-reader.ts";
|
|
28
|
+
import { makeReadResultTool } from "./tools/read-result.ts";
|
|
26
29
|
|
|
27
30
|
type Env = Record<string, string | undefined>;
|
|
28
31
|
|
|
@@ -90,15 +93,22 @@ function sendFollowUp(pi: ExtensionAPI, content: string): void {
|
|
|
90
93
|
* `isBridgeMode()` is true. Connects the broker client (capped retries; a failed connect
|
|
91
94
|
* degrades to a mute executor, never a crash — spec §4), registers the two wire-bound
|
|
92
95
|
* comm-plane tools, surfaces inbound `deliver`/`steer` frames as follow-ups, and closes the
|
|
93
|
-
* client on `session_shutdown`.
|
|
96
|
+
* client on `session_shutdown`. An explicit private reader token also installs read_result;
|
|
97
|
+
* its handshake/read failures are runtime errors, never ordinary mute degradation. */
|
|
94
98
|
export function installBridge(pi: ExtensionAPI, ctx: ExtensionContext, deps: InstallBridgeDeps = {}): void {
|
|
95
99
|
const env = deps.env ?? process.env;
|
|
96
100
|
const endpoint = env.PI_PERSONA_BUS;
|
|
97
101
|
const handle = env.PI_PERSONA_HANDLE;
|
|
102
|
+
const readerToken = env.PI_PERSONA_RESULT_READER_TOKEN;
|
|
103
|
+
if (readerToken !== undefined && (!endpoint || !handle || !isReaderToken(readerToken))) {
|
|
104
|
+
throw new Error("Invalid result reader environment.");
|
|
105
|
+
}
|
|
98
106
|
if (!endpoint || !handle) return; // isBridgeMode() gates the real caller; malformed env degrades to a no-op
|
|
99
107
|
|
|
100
|
-
const
|
|
101
|
-
const
|
|
108
|
+
const canUseBus = env.PI_PERSONA_BUS_TOOLS !== "off";
|
|
109
|
+
const wantsPeers = canUseBus && env.PI_PERSONA_PEERS === "1";
|
|
110
|
+
const clientDeps: MakeBrokerClientDeps = { endpoint, handle, ...(wantsPeers ? { peers: true } : {}),
|
|
111
|
+
...(readerToken !== undefined ? { readerToken } : {}) };
|
|
102
112
|
const client = (deps.makeClient ?? makeBrokerClient)(clientDeps);
|
|
103
113
|
|
|
104
114
|
// Blocking asks are honoured only when the spawning run is async (`PI_PERSONA_ALLOW_BLOCKING`,
|
|
@@ -108,7 +118,11 @@ export function installBridge(pi: ExtensionAPI, ctx: ExtensionContext, deps: Ins
|
|
|
108
118
|
// (the same safety default the in-process engine applies; see contact.ts / spec §4.9).
|
|
109
119
|
const allowBlocking = env.PI_PERSONA_ALLOW_BLOCKING === "1";
|
|
110
120
|
const bus = new BrokerBus(client);
|
|
111
|
-
pi.registerTool(makeContactSupervisorTool(bus, handle, SUPERVISOR_HANDLE, { allowBlocking }));
|
|
121
|
+
if (canUseBus) pi.registerTool(makeContactSupervisorTool(bus, handle, SUPERVISOR_HANDLE, { allowBlocking }));
|
|
122
|
+
if (readerToken !== undefined) {
|
|
123
|
+
pi.registerTool(makeReadResultTool({ read: (request, signal) => bus.connected
|
|
124
|
+
? client.read(request, signal) : Promise.resolve(readFailure("unavailable")) }));
|
|
125
|
+
}
|
|
112
126
|
|
|
113
127
|
// The engine-scoped peer roster (B7): fetched from the host's `list` frame and cached —
|
|
114
128
|
// `contact_peer` accepts an awaitable provider, so the first call can wait for the initial
|
|
@@ -147,7 +161,7 @@ export function installBridge(pi: ExtensionAPI, ctx: ExtensionContext, deps: Ins
|
|
|
147
161
|
peerRefresh = shared;
|
|
148
162
|
return shared;
|
|
149
163
|
};
|
|
150
|
-
pi.registerTool(
|
|
164
|
+
if (wantsPeers) pi.registerTool(
|
|
151
165
|
makeContactPeerTool(bus, handle, {
|
|
152
166
|
listPeers: () => refreshPeers(),
|
|
153
167
|
}),
|
|
@@ -193,9 +207,11 @@ export function installBridge(pi: ExtensionAPI, ctx: ExtensionContext, deps: Ins
|
|
|
193
207
|
}
|
|
194
208
|
},
|
|
195
209
|
() => {
|
|
196
|
-
//
|
|
197
|
-
// a
|
|
198
|
-
if (env.PI_PERSONA_DEBUG) process.stderr.write(
|
|
210
|
+
// Ordinary comms degrade to a mute executor. An installed read_result remains
|
|
211
|
+
// fail-closed: a failed handshake cannot expose a fallback body.
|
|
212
|
+
if (env.PI_PERSONA_DEBUG) process.stderr.write(readerToken !== undefined
|
|
213
|
+
? "[pi-persona] bridge: result reader unavailable\n"
|
|
214
|
+
: `[pi-persona] bridge: could not connect to ${endpoint}\n`);
|
|
199
215
|
try {
|
|
200
216
|
ctx.ui.setStatus("persona-bridge", "⇄ offline");
|
|
201
217
|
} catch {
|
|
@@ -205,6 +221,7 @@ export function installBridge(pi: ExtensionAPI, ctx: ExtensionContext, deps: Ins
|
|
|
205
221
|
);
|
|
206
222
|
|
|
207
223
|
pi.on("session_shutdown", () => {
|
|
224
|
+
bus.connected = false;
|
|
208
225
|
client.close();
|
|
209
226
|
});
|
|
210
227
|
}
|