Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
## [Unreleased]

### Added
- **AI reaction agent — reaction-tool & docs sync.** Documented the platform's AI reaction agent on the reaction surface. `createReaction` / `updateReaction` now describe **`extra.mcp`** (set `true` to hand a reaction to the agent — it processes the reaction under the requesting user's access via this MCP server and posts its answer back as a child **`ai`** reaction) and gained a **`reasoning`** body param (`{inProgress, thoughts:[{id,text,createdAt}]}` — the agent's streamed progress/answer trace). Expanded the `simulator-reactions` skill (new "AI agent reactions" section, with a don't-loop note) and `docs/entities/reactions.md` (interaction-scheme diagram, field table, the `ai` type). Tool-surface descriptions/params only — no new operations; drift gate unaffected.
- **`getCorezoidProcesses` tool (v1.10.0).** Lists the Corezoid processes available to an actor — the ones shared to it via its access API keys — answering "what functions/processes can this actor call?". Public route `GET /papi/1.0/actors/corezoid_processes/{actorId}` already carried the `operationId`; re-dumped `papi-openapi.json` to include it, added the curated tool (`actors.go`), an eval scenario, and the README / `docs/ARCHITECTURE.md` Actors row.
- **Behavioural eval hardened to green in both dry and live (v1.10.0).** Drove the behavioural eval (`cmd/evalrunner`) from 88/103 dry + 84/108 live to **~103/103 dry** and all previously-failing live scenarios passing. Runner gains: **any-of tool groups** (`"a|b"` in a scenario's `tools`, satisfied if either is called — for interchangeable tools like `getTransfer|getTransferByRef`, `getRelatedActors|getLinkedActors`); **`dryOnlyTools`** + **`argCheck.dryOnly`** (assert the follow-up step of a real-dependency flow only in dry, where fixtures let the model reach it — live scores the reachable primary call, since a placeholder-id 404 correctly stops the chain); and **dry fixtures for the new tools** (users, system-actor, attachments, form-accounts/tree, reactions, counters, transfers…) so multi-step dry scenarios resolve. Diagnosed every failure as a *test/environment* artifact (overlap-strictness, missing fixture, underspecified or ellipsis-`id` prompt, or live chain-abort on non-existent entities) — **zero skill/tool defects**. Fixed the scenarios accordingly (concrete ids/refs, full UUIDs, `getUsers`-based discovery, directive prompts) and corrected one genuinely-wrong scenario (`getAllLayerPlacements` is layer→actors, not actor→layers). `eval_test.go` validates the new `dryOnlyTools` / any-of syntax. Documented the knobs in `AGENTS.md`.
- **Form-account & form-tree tools (v1.10.0).** Exposed the form-level account model and UAT navigation. **`createFormAccount` / `getFormAccounts` / `removeFormAccount`** define **default accounts on a form template** that the backend auto-creates on every actor of that form (and removes from all of them on delete) — the clean way to shape a whole form's account model. **`getFormsTree` / `getLinkedForms`** navigate the form tree (UAT: root + descendants / direct parents+children). **Backend (pong-server):** added `operationId`s on the public `/papi/1.0/form_accounts/*` routes, **created a new public `forms_graph` router** (`getFormsTree`/`getLinkedForms`, previously internal-only) and wired it into `apiRegister`, added missing `tags` to the form-account/form-graph schemas, and **fixed a schema/handler mismatch** in `createFormAccountReq` (the public schema documents `accountType` but the handler only read `type` → always defaulted to `fact`; it now accepts `accountType`, falling back to `type`). Re-dumped `papi-openapi.json`. Rewrote the stale "accounts attach to actors, not the form" section of `simulator-forms` (now: form-level default accounts vs ad-hoc per-actor) and updated the form-tree notes in `simulator-forms`/`simulator-actors` (traversal is now curated). Added eval scenarios + README / `docs/ARCHITECTURE.md` Forms row.
Expand Down
52 changes: 52 additions & 0 deletions plugins/simulator/docs/entities/reactions.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ Reactions are organized in a hierarchical tree structure using the ActorsTreeEdg
| edge_type_id | Integer | ID of the edge type (defining the reaction type) |
| level | Integer | Nesting level in the reaction tree |
| path | Text | Path string representing the position in the tree |
| extra | JSON | Presentation/flags: `commentStyleType`, `linkedActorId`, `layerPosition`, and `mcp` (see [AI Reaction Agent](#ai-reaction-agent-mcp)) |
| reasoning | JSON | AI-agent trace on `ai` reactions: `{ inProgress, thoughts:[{id,text,createdAt}] }` (see below) |
| created_at | Integer | Unix timestamp of creation time |
| updated_at | Integer | Unix timestamp of last update |

Expand All @@ -48,6 +50,56 @@ The platform supports various reaction types:
- **Reactions** - Emoji-based quick reactions (like, love, etc.)
- **Mentions** - References to other users
- **Tasks** - Assigned work items
- **AI (`ai`)** - The AI agent's reply, produced by the platform's agent in
response to a reaction flagged `extra.mcp` (see [AI Reaction Agent](#ai-reaction-agent-mcp))

## AI Reaction Agent (MCP)

Any reaction created with **`extra.mcp = true`** is handed to the platform's AI
agent. The agent is a Claude run (hosted by the claude-code-api gateway) that uses
**this MCP server** to read and act on the platform — so it operates strictly under
the **requesting user's** access rules (PAPI authorizes every call with a delegated,
short-lived token minted for that user). Its answer is posted back as a child
**`ai`** reaction under the same actor.

### Interaction scheme

```
User (or automation) creates a reaction with extra.mcp=true on actor A
Platform dispatches the AI agent for actor A (per-actor lock → turns are sequential)
│ • runs as the requesting user (delegated Simulator token)
│ • the agent calls THIS MCP server (scoped to that user's access)
Agent reads/acts via MCP tools (actors, forms, graph, finance, reactions, …)
Platform posts a child `ai` reaction and streams the result:
• description ← the answer text (streamed live, then finalized)
• reasoning.inProgress = true while running, false when done
• reasoning.thoughts ← step log (e.g. which tools were used)
```

### Fields

| Field | Where | Meaning |
|-------|-------|---------|
| `extra.mcp` | on the reaction you create | `true` ⇒ hand this reaction to the AI agent |
| `ai` (reaction type) | the agent's reply | child reaction holding the agent's answer |
| `reasoning.inProgress` | on the `ai` reply | `true` while the answer is still being produced |
| `reasoning.thoughts` | on the `ai` reply | `[{id,text,createdAt}]` — the agent's step log |
| `description` | on the `ai` reply | the answer text (partial while `inProgress`) |

### Notes

- **One shared thread per actor.** Turns on a given actor are serialized; the agent
keeps context across turns, so a follow-up `extra.mcp` reaction continues the same
discussion.
- **Reading mid-stream.** An `ai` reaction with `reasoning.inProgress = true` is still
being written — treat its `description` as partial.
- **Avoid loops.** Set `extra.mcp` only on genuine (human) requests, not on the agent's
own output. The platform bounds runaway chains, but it still wastes turns.

## API Endpoints

Expand Down
20 changes: 17 additions & 3 deletions plugins/simulator/mcp-server/internal/tools/reactions.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package tools

// reactionTypes are the backend REACTION_TYPES — the kind of reaction/event
// placed under an actor. `comment` is the everyday "post a note" type.
// placed under an actor. `comment` is the everyday "post a note" type; `ai` is
// the AI agent's reply, normally produced by the platform's agent (see the
// `extra.mcp` flag and the AI-agent flow in docs/entities/reactions.md).
var reactionTypes = []string{"view", "comment", "ai", "rating", "sign", "ds", "done", "reject", "freeze"}

// reactionExtraDesc documents the optional `extra` presentation object.
const reactionExtraDesc = "Optional presentation object: " +
"{commentStyleType:\"primary\"|\"secondary\"|\"text\", linkedActorId:string, " +
"layerPosition:{x:number,y:number}}."
"layerPosition:{x:number,y:number}, mcp:boolean}. " +
"mcp:true hands the reaction to the AI agent — it processes the reaction under " +
"the requesting user's access (via this MCP server) and posts its answer back " +
"as a child `ai` reaction. See docs/entities/reactions.md."

// reactionReasoningDesc documents the optional `reasoning` object — the AI
// agent's progress/answer trace, carried on `ai` reactions.
const reactionReasoningDesc = "Optional AI-agent trace: " +
"{inProgress:boolean, thoughts:[{id:string,text:string,createdAt:number}]}. " +
"On `ai` reactions the agent streams progress here (inProgress=true while " +
"running, thoughts = step log) while the answer text fills `description`."

// reactionOps — reactions are events/comments that live under a parent actor
// (and are themselves actors). In every path, `{actorId}` is the PARENT (root)
Expand All @@ -19,13 +31,14 @@ var reactionOps = []Operation{
Summary: "Add a reaction (comment/event) under an actor. Use type=comment for a plain text note. " +
"The reaction becomes a child of the actor; reply to another reaction by passing its id as parentId.",
Params: []Param{
{Name: "type", In: InPath, Type: "string", Required: true, Enum: reactionTypes, Desc: "Reaction kind (comment for a note)."},
{Name: "type", In: InPath, Type: "string", Required: true, Enum: reactionTypes, Desc: "Reaction kind (comment for a note; ai is the agent's reply, usually platform-produced)."},
{Name: "actorId", In: InPath, Type: "string", Required: true, Desc: "Parent (root) actor UUID the reaction is placed under."},
{Name: "description", In: InBody, Type: "string", Desc: "The reaction text (e.g. the comment body)."},
{Name: "data", In: InBody, Type: "object", Desc: "Optional structured payload carried on the reaction."},
{Name: "parentId", In: InBody, Type: "string", Desc: "Optional id of the reaction this one replies to (threading)."},
{Name: "hidden", In: InBody, Type: "boolean", Desc: "Create the reaction hidden."},
{Name: "extra", In: InBody, Type: "object", Desc: reactionExtraDesc},
{Name: "reasoning", In: InBody, Type: "object", Desc: reactionReasoningDesc},
{Name: "attachments", In: InBody, Type: "array", Desc: "Optional attachments: array (1-20) of {attachId:int} (ids from uploadBase64 / getAttachments)."},
{Name: "notify", In: InQuery, Type: "boolean", KeepFalse: true, Desc: "Send notifications about the new reaction (default true). Set false to post quietly."},
{Name: "sipTranscription", In: InQuery, Type: "boolean", Desc: "Mark the reaction as a SIP transcription entry."},
Expand All @@ -42,6 +55,7 @@ var reactionOps = []Operation{
{Name: "parentId", In: InBody, Type: "string", Desc: "New parent reaction id (re-thread)."},
{Name: "hidden", In: InBody, Type: "boolean", Desc: "Hide/unhide the reaction."},
{Name: "extra", In: InBody, Type: "object", Desc: reactionExtraDesc},
{Name: "reasoning", In: InBody, Type: "object", Desc: reactionReasoningDesc},
},
},
{
Expand Down
29 changes: 28 additions & 1 deletion plugins/simulator/skills/simulator-reactions/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ target actor's UUID (use `simulator-actors` / `searchActors` to find it) before

```
createReaction(
type="comment", # the everyday note; other types: view, ai, rating, sign, ds, done, reject, freeze
type="comment", # everyday note; other types: view, rating, sign, ds, done, reject, freeze; ai = agent reply (see below)
actorId="<parent actor UUID>",
description="Looks good to me", # the comment text
notify=true) # default true; set false to post quietly
Expand Down Expand Up @@ -87,6 +87,33 @@ getReactionsStats(actorId="<parent actor UUID>") # counts by
- `view=tree` returns the nested reply tree; `flat` a flat list; `thread` a single thread.
- `parentId` narrows to the replies under one reaction; `from`/`to` filter by created time.

## AI agent reactions (`extra.mcp`)

A reaction can be handed to the platform's **AI agent** by creating it with
`extra.mcp = true`. The platform then runs the agent on that reaction **under the
requesting user's access** (the agent uses this same MCP server, so it can only
read/write what the user can) and posts the answer back as a child **`ai`** reaction.

```
createReaction(type="comment", actorId="<actor UUID>",
description="Summarize this actor and flag anything unusual",
extra={ "mcp": true }) # → triggers the AI agent; its reply appears as a child `ai` reaction
```

The `ai` reply carries a `reasoning` object that the agent streams while it works:

- `reasoning.inProgress` — `true` while the agent is still producing the answer.
- `reasoning.thoughts` — `[{ id, text, createdAt }]`, a step log (e.g. tools it used).
- `description` — fills with the answer text (streamed live over WebSocket, then finalized).

So when reading a discussion (`getReactions`), an `ai` reaction with
`reasoning.inProgress = true` is still being written; treat its `description` as partial.

> **Don't loop:** the agent already replies on its own. Only set `extra.mcp` on a
> *human* request you want the agent to handle — not on the agent's own output. The
> platform bounds runaway chains, but creating `extra.mcp` reactions from agent
> output wastes turns.

## Pin & read state

```
Expand Down
Loading