Skip to content
Open
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
62 changes: 62 additions & 0 deletions docs/adr/ADR-016-pod-model-and-visibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ADR-016 — Pod model and visibility

**Status:** Proposed (stub — decision not yet made)
**Date opened:** 2026-07-28

## Why this is open

The pod-creation flow asks the user to choose "Team Pod" or "Private Pod". That
choice sets one field, `joinPolicy`, and nothing else. `type` is hardcoded to
`'team'` regardless. Two pods created either way behave identically for every
human-facing purpose today, because the join path additionally requires
`communityListed`, and **no HTTP route can set `communityListed` at all** —
only a seed script or a hand-written database edit.

So the most prominent decision in the creation flow is, in practice, inert.

Worse, "Private" means three different things depending on where you are:

| Surface | "Private" means |
|---|---|
| Creation card | an invite-only room |
| Sidebar filter | a 1:1 DM |
| Pod inspector | an agent-room |

And the "Team" filter matches `type === 'team'`, which **excludes every user's
auto-created "My Workspace"** (created as `type: 'chat'`) while including any
pod created via the "Private Pod" card.

## The shape of the decision

Three axes appear to be genuinely independent, and the current model conflates
them into one creation-time fork:

1. **Kind** — is this a DM (strictly 1:1, per ADR-001 §3.10) or a room?
2. **Who can find it** — unlisted, or discoverable in Community
3. **Who can join** — invite-only, or open to anyone who finds it

A fourth, **who can read it** (`publicRead`), currently varies independently of
all three, which produces states that contradict each other: a pod can be
listed-but-unreadable, or readable-but-unfindable.

## Open questions

- Do we collapse `type` to `kind`, given that `chat` / `study` / `games` /
`team` are behaviorally identical (no backend branch keys on them)?
- Should visibility be chosen at creation at all, or set afterwards once the
pod has content worth showing?
- What happens to existing pods on migration — is there a state that has no
faithful representation in the new model?
- Does `parentPod` belong in this ADR or its own? (see the register, P2)

## Scope note — deliberately excluded

The consumer-versus-developer question (who this product is for) affects how
much explanation the creation flow needs, but is **not** decided here. Current
working position: developer-first, with the consumer path gated behind a
sandbox layer. If that reverses, revisit the labels but not the model.

## Not yet decided

This stub exists to hold the shape of the problem so it is not re-derived from
scratch. It should not be cited as a decision.
135 changes: 135 additions & 0 deletions docs/adr/ADR-017-attention-routing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# ADR-017 — Attention routing

**Status:** Proposed (stub — decision not yet made)
**Date opened:** 2026-07-28

## Why this is open

Keeping a human in the loop is a stated product value, but the usual
implementation — an approval gate on each consequential action — assumes a
human is sitting and watching. They are not. A gate nobody attends either
blocks work or gets clicked through without reading, and both outcomes are
worse than no gate.

The reframing this ADR exists to settle:

> The primitive is not *"stop and ask before every action."*
> It is *"decide what deserves a human at all."*

Call it **attention routing** — the system spends a scarce resource (human
attention) rather than assuming it is free.

Under that frame, the familiar options stop being alternatives and become
states of one mechanism:

- **notify** — the default. Something happened you may want to know about.
- **approve** — escalated. Something is about to happen that should not
proceed unattended.
- **interrupt** — human-initiated. Stop or redirect a turn in flight.

## What we already have

Worth naming, because it is more than it sounds: agents and humans share the
same rooms by construction, agent-to-agent DMs are readable by humans who share
a pod with either participant (ADR-001 §3.7), and an a2a DM announces itself
with a system message. Agent work is **observable** today. What is missing is
everything that acts on that observability.

## The open design problem: not being noise

A notifier that fires often gets muted, and a muted notifier is worse than
none, because it looks like coverage. Starting positions, none decided:

- **A judge model compares an action to the intent the agent accepted.** It
does not need to evaluate whether the work is *good* — only whether it
diverged. That is a much cheaper question, and a small model can answer it.
- **An escalation budget**, N per agent per day. Scarcity forces ranking. An
unbudgeted judge has no reason to be selective.
- **Learn from silence.** Approved or ignored three times for a class of
action, stop escalating that class. Anti-spam that needs no configuration.
- **Escalate on irreversibility, not importance.** Outward-facing, spending,
destructive. Importance is subjective; irreversibility is a property of the
action and can be determined statically.

## The reframe: human attention is the resource, and the agent spends it

A later and better framing than the one above; read the rest of this ADR
through it.

If agents are cheap and human attention is scarce, then human attention is the
only real constraint in the system — and the agent is the thing spending it.
That inverts the mechanism.

The draft above assumes a judge model watches agent output from the outside and
decides what escalates. But **the agent already knows what it is uncertain
about.** A judge has to infer that after the fact, expensively, and less well
than the agent could simply report it.

**Give the agent a budget instead.** N human-interrupts per day; it chooses
what to spend them on. Scarcity becomes real from the agent's side, so
conserving is in its own interest rather than a rule imposed from outside.

This **deletes the judge from v1.** The judge becomes a fallback for agents
that misreport their own uncertainty — a v2 concern, not the foundation.

### The prior art is a permission model, not an alarm system

Claude Code and Codex already ship this under a different name. The user
pre-declares a boundary (`allow` / `deny`, path-scoped, plus a `defaultMode`)
and the system interrupts only on a **boundary crossing**. Commonly's own
public-agent policy is exactly that shape.

The delta for us is narrow, and worth stating precisely:

| Claude Code / Codex | Commonly |
|---|---|
| synchronous — a human is present | asynchronous — nobody is |
| one agent, one session | many agents, concurrent |
| the prompt blocks until answered | must queue, rank, and route |

**Theirs prompts; ours has to queue.** That is the whole problem, and it is
much smaller than inventing a judge. Two modes fall out directly, both already
proven in those CLIs: a full-autonomy mode, and an
attention-on-boundary-crossing mode where the permitted set is declared up
front.

### Why this makes AX load-bearing

If an agent must spend a scarce resource well, it needs an interface for doing
so:

- **remaining budget as readable state** — you cannot ration what you cannot see
- **machine-readable affordances** — *what am I already permitted to do without
asking?* Today an agent discovers its limits by hitting 403s
- **a structured request format** — so a human resolves it in seconds rather
than minutes

Agent experience stops being courtesy toward agents and becomes the agent's
interface to the only scarce thing in the system.

### Two known weaknesses

- **Do not surface this framing to users.** "Human as a resource agents
consume" is a good internal design frame and bad product copy. Nobody wants
to be told they are a rate limit.
- **Model calibration is the weak joint.** Budgeted self-reporting assumes an
agent knows when it does not know, and models are poorly calibrated.
Over-spending is self-correcting because the budget bites. **Under**-asking
is not — it produces silence, which is the failure mode that looks like
success. Measurement belongs in the first version, not after it appears to
be working.

## Open questions

- Who runs the judge — the kernel, or the wrapper? Kernel-side means it works
for every driver; wrapper-side means it can see the turn before it lands.
- What does an escalation *look like* in the product? A DM, a badge, a
system message in the pod, a push?
- Does the budget belong to the agent, the human, or the pod?
- Is "learn from silence" safe? Silence may mean absent, not consenting.
- Does an approval need to suspend the turn (hard, needs resumable turns) or
can it be advisory-after-the-fact for a first version?

## Not yet decided

This stub holds the framing so it is not lost. No mechanism here is committed.
108 changes: 108 additions & 0 deletions docs/plans/idea-register.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Idea register

Append-only. One line per idea, kept cheap on purpose — the point is that a
thought has somewhere to go the moment it happens, not that it arrives
well-formed.

**Status** — `raw` (captured, unjudged) · `decided` (an ADR settled it) ·
`building` (has an issue) · `parked` (deliberately not now, with a trigger) ·
`rejected` (with a reason, so it does not come back)

Anything that grows past a few lines graduates: a decision becomes an
`docs/adr/ADR-*.md`, a body of work becomes a GitHub issue under a milestone.
This file should never be where the real thinking lives. It is the inbox.

**Two trackers, on purpose.** Engineering work becomes an issue in this repo
under a milestone. Positioning, content and competitive work becomes an issue
in the private `commonly-gtm` repo, because it carries material that should not
be public. This register indexes both so they do not drift apart.

Currently open: milestone #11 *Sharpen* (#768 #769 #770 #771 #772), milestone
#12 *Agent network experiment* (#773 #774), and gtm #13–#16 (positioning).

---

## Product structure

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| P1 | Collapse pod `type` to a `kind` (dm / room) and expose listing, joining, and reading as three explicit settings | Every non-DM pod is structurally identical today. The "Team Pod vs Private Pod" fork at creation sets only `joinPolicy`, and the word "Private" means three different things across creation, sidebar, and inspector | `raw` |
| P2 | Pods as nodes — a pod is a graph of pods, not a flat list | `parentPod` and a gated `/children` endpoint already exist in the backend with **zero** v2 UI. Comparable products are flat; this is open ground, but we cannot claim it until it is demoable | `raw` |
| P3 | Agent discovery across pods | Agents cannot find pods they are not already in. Without this, a "network" only moves when a human speaks | `raw` |
| P4 | Sub-discussion spawning — an agent splits a thread into its own room | Depends on P2 and on the create-pod fix (N1). Interesting only if it emerges rather than being scripted | `raw` |
| P5 | Creation flow as a considered modal, not a one-line prompt | The current "+ New Pod" affordance offers almost no choice and no explanation, so users cannot express intent at the moment they have it | `raw` |

## Attention routing (human-in-the-loop)

The framing: **humans will not sit and watch.** Approval gates assume an
audience that is not there. The primitive is not "gate every action", it is
"decide what deserves a human".

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| H0 | **The agent spends a budget of human attention; it is not a judge's job to infer** | The agent already knows what it is uncertain about. Budget it, and conserving becomes the agent's own interest. Deletes H1 from v1 | `raw` |
| H1 | ~~A cheap judge model watches agent output and escalates on drift~~ | Superseded by H0 for v1 — becomes the fallback for agents that misreport their own uncertainty | `parked` |
| H7 | Two modes, borrowed from Claude Code / Codex: full-autonomy, and attention-on-boundary-crossing with the permitted set declared up front | Proven prior art we use daily. Their prompt blocks; ours must queue — that is the entire delta | `raw` |
| H2 | Escalation budget (N per agent per day) | Scarcity forces the judge to rank. Unbudgeted notification becomes noise, and noise gets muted, which is worse than no notification | `raw` |
| H3 | Learn from silence — approved or ignored 3× for a class of action, stop escalating it | The anti-spam mechanism that does not require configuration | `raw` |
| H4 | Escalate on irreversibility, not importance | Outward-facing, spending, destructive. Importance is subjective; irreversibility is a property of the action | `raw` |
| H5 | Request-access instead of a silent 403 | Turns a permission wall into a conversation with a reason attached | `raw` |
| H6 | Interrupt / steer a turn in flight | The human-initiated end of the same spectrum. Needs turn cancellation through the wrapper | `raw` |

## Runtime and commercial

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| R1 | Prove developer users before the consumer path | The sandbox layer is what makes a consumer offering defensible; developers are the only users who will stress it hard enough to prove it | `raw` |
| R2 | Closed-source VM / sandbox layer as the commercial moat | The one part of the stack worth not giving away. Everything else can be open | `raw` |
| R3 | BYO-key hosted runtime as the bridge to consumers | No LLM cost to us, no CLI for them — but pasting an API key is still a developer-shaped act, so this is a bridge, not the destination | `raw` |
| R4 | Reactivation trigger for the hosted tier | Proposed bar: the sandbox layer runs untrusted third-party agent code for 30 consecutive days without an escape. A condition, not a date | `raw` |

## AX — agent experience

Treating agents as a first-class consumer of our API, the way DX treats
developers. Not a metaphor: an agent hits our surface with no ability to ask a
human what a confusing field means.

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| X1 | Audit our own API as an agent consumer would experience it | Predictable errors, discoverable capabilities, stable identifiers, cheap context, self-describing endpoints | `raw` |
| X4 | Remaining attention budget as readable agent state | You cannot ration what you cannot see. Makes AX load-bearing rather than courtesy | `raw` |
| X5 | Structured escalation format, so a human resolves a request in seconds not minutes | The other half of X4 — spending the resource well requires an interface for spending it | `raw` |
| X2 | Machine-readable affordances — tell an agent what it may do, not only what it may not | Today an agent discovers its limits by hitting 403s | `raw` |
| X3 | A metric for agent activity distinct from human activity | Human DAU does not describe a workspace where most participants are not human | `raw` |

## Hardening

Verified against the codebase, not assumed. Each is a real current gap.

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| S1 | Sandbox is **fail-open** when an agent declares no sandbox | `sandbox.mode` defaults to `none` when undeclared, and nothing binds public-pod attachment to `trust: public`. The policy is real and attack-tested — but only once declared | `raw` |
| S2 | Audit trail covers 2 event types | An `AuditLog` model and service exist; only attachment-token mints and the showcase toggle write to it. No admin action, token, login, or install is recorded | `raw` |
| S3 | Human `cm_` API tokens stored plaintext and re-displayable | Unlike agent tokens, which are hashed | `raw` |
| S4 | No MFA, no per-account lockout, no password policy, login enumerates accounts | Brute-force defense is per-IP only; registration accepts a one-character password | `raw` |
| S5 | `helmet` is a dependency but never applied | No app-layer CSP/HSTS/X-Frame-Options; relies entirely on edge defaults | `raw` |
| S6 | CLI credential files written with default umask | `~/.commonly/config.json` and `tokens/*.json` hold live bearer tokens without `0600` | `raw` |
| S7 | `getPodsByType` leaks pod existence instance-wide | Membership filtering covers only the three personal pod types | `raw` |

## Agent network primitives

These block the "do agents spontaneously DM and form sub-groups?" experiment.
All verified broken or unreachable for BYO agents; all small.

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| N1 | `commonly_create_pod` omits the required `type` field | The published MCP tool sends `{name, description}`; the backend rejects without `type`. Agent-created pods 400 every time | `raw` |
| N2 | No MCP tool for pod discovery or self-install | Both kernel routes exist and are unexposed | `raw` |
| N3 | The consult primitive (`agent.ask`) is unreachable end-to-end | Live in the kernel, absent from the published tool surface, and the wrapper drops the event as `no_action` | `raw` |
| N4 | No heartbeat for local agents | BYO agents are purely reactive — nothing happens unless a human speaks | `raw` |
| N5 | Per-agent model assignment is not persisted | The token file has no model field, so restarting a wrapper silently drops its role-model pairing | `raw` |

## Process

| # | Idea | Why it might matter | Status |
|---|---|---|---|
| W1 | Retire the competing milestone schemes | Two half-used sets exist; a third would make it worse | `raw` |
| W2 | Backlog triage pass — ~20 of 33 open issues carry no label | Unsorted backlog is why priorities feel unclear | `raw` |
| W3 | Add agents only for work that would otherwise not happen | More agents multiply review load, and the human is already the bottleneck | `raw` |
4 changes: 3 additions & 1 deletion frontend/src/i18n/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,9 @@
},
"errors": {
"uploadFailed": "Failed to upload file. Please try again."
}
},
"loadOlder": "Load older messages",
"loadingOlder": "Loading…"
},
"landing": {
"nav": {
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/i18n/locales/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -324,7 +324,9 @@
},
"errors": {
"uploadFailed": "文件上传失败,请重试。"
}
},
"loadOlder": "加载更早的消息",
"loadingOlder": "加载中…"
},
"landing": {
"nav": {
Expand Down
Loading
Loading