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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,6 @@ extension.pem
ModelReport.md
session-*.md
.claude/settings.json

# local backups (never commit)
api-proxy-cf.backup-*/
122 changes: 122 additions & 0 deletions INTERSESSION_DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Inter-Session Communication — Design Spec

Status: **IMPLEMENTED** (CLI). Built on the existing `BUS`/`send_message`
infrastructure rather than a parallel channel.

## What shipped
- `src/agent/sessionRegistry.js` — live session registry (keyed by agent label).
`registerSession()` broadcasts a creation notice to every other session's
mailbox; `trackToolFiles()` records recently-touched files per session.
- `src/agent/agent.js` — every `Agent` registers itself (with model) and updates
its goal/status each turn; file-touching tools feed `trackToolFiles`.
- `src/agent/tools.js` — `list_sessions` (peer discovery) and `query_session`
(returns a peer's goal/status + recently-touched files; optional `question`
delivered to the peer's inbox). Both added to the hosted-model allowlist and
the grant-independent set.
- `src/tui/App.jsx` — hidden `/create-external-model <https://url> <model> <key>`
dev command (absent from `COMMANDS`, so it never tab-completes).

## Known gap
- The user-facing `main` agent does not see creation notices via `read_messages`
(BUS routes `to:"main"` to an internal inbox that `read_messages` doesn't read;
`main` can still call `list_sessions` to discover peers). Spawned sub-agents
*are* notified correctly. Fixing `main` would require changing `read_messages`
and risks colliding with the spawn flow's own `readMain()` consumption.

## Original design notes (kept for reference)


## Goal (from request)

1. Code-chat **sessions** can talk to each other.
2. A model is **notified when another session is created**.
3. A session can **ask another session what it's doing / how to avoid each other**.
4. A **tool** the model can call to do the above.
5. A **developer command** `/create-external-model <https://url> <model name> <api key>`
that registers an external OpenAI-compatible model — and is **deliberately NOT
tab-completable**.

## Integration reality (from reading the code)

- Slash-command dispatch = `runCommand(raw)` in `src/tui/App.jsx` (big `switch`).
- `src/ui/commands.js` `COMMANDS` array is **only** for suggestions + tab
completion (`getSuggestions` / `getTabCompletion`). A command omitted from
`COMMANDS` still runs via the `runCommand` switch but never autocompletes.
→ `/create-external-model` is hidden simply by not listing it in `COMMANDS`.
- Custom endpoints live in `CUSTOM_ENDPOINTS` (mutable, `src/config.js`); the
`/endpoint` handler (App.jsx ~2168) shows the exact mutate + `saveCustomEndpoints(...)`
pattern to mirror.
- Tools are defined in `src/agent/tools.js`; agents get them filtered via
`agentRegistry.filterTools` (permission rulesets).
- Sessions/agent loop: `src/agent/agentRegistry.js` (named agents, not live
sessions) + the live chat loop in `src/tui/App.jsx` / `src/agent/agent.js`.

## Proposed design

### 1. `src/agent/sessionRegistry.js` (new, process-wide singleton)
In-memory registry of **currently-running** sessions (live coordination only —
no persistence needed).

Record shape:
```
Comment thread
Ravikxx marked this conversation as resolved.
{ id, name, model, goal, status, owner, createdAt, lastActivity, running, turnCount }
```
API:
- `register(session)` / `unregister(id)`
- `list()` → public descriptors (omit sensitive fields)
- `get(id)`
- `updateStatus(id, { goal, status })` — called each turn so peers can answer
"what are you doing"
- `notifyCreation(session)` — enqueue a creation notice into every *other*
session's `inbound` queue
- per-session `inbound` queue drained at the start of each agent turn

### 2. Creation notifications
Hook `register()` + `notifyCreation()` wherever a new chat/code-session spawns
(new chat in App.jsx; any spawned agent loop in agent.js). Each other live
session drains its `inbound` queue at the top of its next turn and surfaces the
notice as a `system` message:
`"New session '<name>' started on model <model> — goal: <goal>."`
(Draining at turn-start avoids interrupting a mid-turn agent.)

### 3. Model tools (`src/agent/tools.js`)
- `list_sessions` → returns peer sessions (id, name, model, status, goal),
**excluding the calling session itself**.
- `query_session({ sessionId, question })` → "what are you working on / how
should we avoid conflicts?". **v1 = synchronous status lookup**: returns the
target's last `goal` + `status` + recent file activity (from the registry
record), not a full back-and-forth. True async peer-to-peer chat = v2.
- Both gated through `agentRegistry.filterTools` so denied/allowed tool rules
still apply. `query_session` must never return another session's full message
history — only goal/status + summarized activity (privacy boundary).

### 4. `/create-external-model` (hidden dev command)
Signature: `/create-external-model <https://url> <model name> <api key>`
Handler (new `case` in `runCommand`, App.jsx), mirroring `/endpoint`:
```
CUSTOM_ENDPOINTS[name] = { baseURL: url, model: name, apiKey: key, context: 0 }
CONTEXT_WINDOWS[name] = <fetched or default>
saveCustomEndpoints({ ...CUSTOM_ENDPOINTS })
setModel(name); agentRef.current?.setModel(name); saveModel(name)
Comment thread
Ravikxx marked this conversation as resolved.
```
Validation: `url` must start with `http(s)://`. Gated as developer-only
(undocumented; always available but absent from `COMMANDS`, so no tab-complete).
Comment thread
Ravikxx marked this conversation as resolved.
Comment thread
Ravikxx marked this conversation as resolved.

## Open questions to resolve before implementing
- **Scope**: CLI sessions only, or also the desktop "code chats" (Axion App
Code tab)? Tools/slash-commands here are CLI-only.
- **Session definition**: a whole chat, or a spawned sub-agent? Affects where
`register()` is hooked.
- **v1 vs v2** for `query_session`: status-polling (simple, synchronous) vs.
real async agent-to-agent messaging (needs a request/response channel +
timeout). Recommend v1 status-polling first.
- Notification timing: turn-start drain (chosen) vs. push interrupt.

## Files touched (when implemented)
- new: `src/agent/sessionRegistry.js`
- `src/agent/tools.js` (2 tools)
- `src/tui/App.jsx` (runCommand: creation hook + `/create-external-model` case;
model tool available to tools list)
- `src/agent/agent.js` (drain `inbound` + `updateStatus` each turn)
- `src/ui/commands.js` — **NOT** modified for the hidden command (intentionally
absent so it stays out of tab-completion).
20 changes: 20 additions & 0 deletions api-proxy-cf/migrations/042_client_errors.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Client-side error reports from the iPhone app (and any future native client).
-- The app shows the user only a generic "Something went wrong" and ships the
-- real failure here so we can triage crashes and exceptions without ever
-- exposing internal details (stacks, underlying errors) to users.
CREATE TABLE client_errors (
id TEXT PRIMARY KEY,
user_id TEXT,
app_version TEXT,
build_number TEXT,
os_version TEXT,
device_model TEXT,
type TEXT,
message TEXT,
stack TEXT,
context TEXT,
created_at INTEGER NOT NULL
);

CREATE INDEX idx_client_errors_created ON client_errors (created_at DESC);
CREATE INDEX idx_client_errors_user ON client_errors (user_id, created_at DESC);
29 changes: 29 additions & 0 deletions api-proxy-cf/migrations/043_usage_boost_and_bulk_reset.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- Migration 043: admin-controlled temporary usage-limit boost, plus an audit
-- trail for the bulk "reset usage for everyone" action.
--
-- usage_boost is a singleton row (id always 1) rather than a general
-- key/value settings table, since there's exactly one thing to configure
-- right now and a singleton is simpler to reason about and query than a
-- generic table would be for a single value. percent=0 or expires_at in the
-- past both mean "no boost currently active" — the app doesn't need a
-- separate enabled flag, an expired/zero boost already reads as inactive.
CREATE TABLE usage_boost (
id INTEGER PRIMARY KEY CHECK (id = 1),
percent INTEGER NOT NULL DEFAULT 0,
expires_at INTEGER,
set_by TEXT,
set_at INTEGER
);
INSERT INTO usage_boost (id, percent, expires_at, set_by, set_at) VALUES (1, 0, NULL, NULL, NULL);

-- One row per bulk reset, not one row per affected user — this is an audit
-- record of the admin action itself ("who did this, when, how many people
-- did it touch"), not a per-user log; per-user history isn't needed since
-- the reset just zeroes usage_week/usage_window the same way a normal
-- period rollover would.
CREATE TABLE admin_bulk_usage_resets (
id TEXT PRIMARY KEY,
admin_email TEXT NOT NULL,
affected_users INTEGER NOT NULL,
created_at INTEGER NOT NULL
);
11 changes: 11 additions & 0 deletions api-proxy-cf/migrations/044_disabled_models.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- Migration 044: admin kill switch for a specific model, independent of any
-- code deploy. Presence of a row means that model_id is disabled — this is
-- deliberately a real DB row (audit-visible: who, when, why) rather than a
-- boolean column somewhere, so disabling something always leaves a record
-- of who did it and, ideally, why.
CREATE TABLE disabled_models (
model_id TEXT PRIMARY KEY,
disabled_by TEXT NOT NULL,
disabled_at INTEGER NOT NULL,
reason TEXT
);
17 changes: 17 additions & 0 deletions api-proxy-cf/migrations/045_guardrail_flags.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- Migration 045: observability log for the Fresco 1.3 real-time output
-- guardrail (judgeFlagged in chatGeneration.js). One row per moderated
-- generation (every MODERATED_MODELS reply that actually produced text),
-- not just the flagged ones — logging SAFE verdicts too is what lets an
-- admin see the guardrail's true fire rate and spot false positives, not
-- just count how many replies got blocked.
CREATE TABLE guardrail_flags (
id TEXT PRIMARY KEY,
generation_id TEXT NOT NULL,
user_id TEXT NOT NULL,
model TEXT NOT NULL,
flagged INTEGER NOT NULL,
user_text TEXT,
assistant_text TEXT,
created_at INTEGER NOT NULL
);
CREATE INDEX idx_guardrail_flags_created_at ON guardrail_flags (created_at);
10 changes: 10 additions & 0 deletions api-proxy-cf/migrations/046_announcement_send_history.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
-- Migration 046: send-history visibility for announcement emails, closing
-- the exact blind spot behind the 2026-08 incident where a GitHub Actions
-- run reported success but the announcement email never actually queued
-- (only caught by reading raw workflow logs, not the green checkmark).
-- recipient_count and send_status let an admin confirm from the dashboard
-- that a given announcement actually reached subscribers, without needing
-- to go dig through Actions logs again.
ALTER TABLE announcements ADD COLUMN recipient_count INTEGER;
ALTER TABLE announcements ADD COLUMN send_status TEXT;
ALTER TABLE announcements ADD COLUMN send_error TEXT;
2 changes: 1 addition & 1 deletion api-proxy-cf/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"test": "node --test test/*.test.mjs",
"check": "node --check src/index.js && node --check src/avatar.js && node --check src/billing.js && node --check src/chatGeneration.js && node --check src/lumen-upstream.js && node --check src/veil-upstream.js && node --check src/status.js && node --check src/sandbox.js && node --check src/auditLog.js && node --check src/messageReview.js && node --check src/moderationAdmin.js && node --check src/webOrigins.js"
"check": "node --check src/index.js && node --check src/avatar.js && node --check src/billing.js && node --check src/chatGeneration.js && node --check src/fresco-upstream.js && node --check src/glyph-upstream.js && node --check src/status.js && node --check src/sandbox.js && node --check src/auditLog.js && node --check src/messageReview.js && node --check src/moderationAdmin.js && node --check src/webOrigins.js"
},
"dependencies": {
"hono": "^4.4.0"
Expand Down
17 changes: 17 additions & 0 deletions api-proxy-cf/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,20 @@ CREATE TABLE IF NOT EXISTS api_keys (
tokens INTEGER DEFAULT 0,
revoked INTEGER DEFAULT 0
);

CREATE TABLE IF NOT EXISTS client_errors (
id TEXT PRIMARY KEY,
user_id TEXT,
app_version TEXT,
build_number TEXT,
os_version TEXT,
device_model TEXT,
type TEXT,
message TEXT,
stack TEXT,
context TEXT,
created_at INTEGER NOT NULL
);

CREATE INDEX IF NOT EXISTS idx_client_errors_created ON client_errors (created_at DESC);
CREATE INDEX IF NOT EXISTS idx_client_errors_user ON client_errors (user_id, created_at DESC);
Loading
Loading