Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
d1b4361
Stop requiring regeneration for cells with no declared design
Sep 8, 2026
12799b1
Derive peer agent cards and mediate consultations
Sep 8, 2026
17efb38
Add the conversation-mode run endpoint
Sep 8, 2026
656735f
Show conversations on the protocol canvas
Sep 8, 2026
0c61963
Clear the stuck AgentAction contract off protocol agents
Sep 8, 2026
2af5009
Add a compose overlay for running against the working trees
Sep 8, 2026
c56c09b
Add Peer Collaboration as a coordination strategy
Sep 8, 2026
352d0f3
Drop the "can consult" caption and the Converse button
Sep 8, 2026
c5eb129
Let a peer loop name its own conversation lead, and run
Sep 9, 2026
1566b05
Take a consultation's sender from the turn stack, not the engine
Sep 9, 2026
91ca13c
Open the canvas on the protocol's most recent run
Sep 9, 2026
fff0623
Move the conversation transcript off the minimap's corner
Sep 9, 2026
257813c
Pin the published spinal protocol's behavior in a golden harness
Sep 9, 2026
fa8d669
Make the Sequential strategy mean an actual chain
Sep 9, 2026
5aac68f
Make a coordination strategy change demand a regeneration
Sep 9, 2026
8ad3773
Pin the agent prompt format behind a version
Sep 9, 2026
00bbe81
Give a cell's workspace named dataset slots
Sep 9, 2026
dfd91f0
Add Supervisor as a coordination strategy
Sep 9, 2026
674c1b0
Let an experiment declare its own workspace stages
Sep 9, 2026
1c67ce8
Add a Design-tab editor for the workspace stage plan
Sep 9, 2026
a17997a
Mount workspace-core in the dev compose overlay
Sep 9, 2026
cb6346e
Grant the tools an unsplit dataset's prompt actually names
Sep 9, 2026
d1e83e7
Remove the unused ad-hoc conversation endpoint
Sep 9, 2026
e531503
Derive the workspace stage plan from the canvas instead of asking
Sep 9, 2026
912e8bc
Pin every prompt contract version byte-for-byte, not just v1
Sep 9, 2026
89b7942
Drop the unanchored prompts/ ignore rule, hold the goldens at fixture…
Sep 9, 2026
9aac1f2
Replace the prompt-contract version ladder with two named contracts
Sep 9, 2026
cac2d49
Tell an agent what it received and who receives its output
Sep 9, 2026
dbdd572
Pass upstream output by reference, not by automatic injection
Sep 9, 2026
c9700f1
Let a prompt say what it wants instead of guessing at it
Sep 9, 2026
9b60c82
Assemble an agent's prompt without running it
Sep 9, 2026
32d82fd
Say what reaches an agent, and what actually reached it
Sep 9, 2026
f803cb6
Lay the Agent inspector out in the direction data travels
Sep 9, 2026
d564e00
Destructure recomputeKey, which was only ever declared in the type
Sep 9, 2026
f9c5308
Let the System prompt reference upstream output too
Sep 9, 2026
5c3b757
Let an agent say what shape its answer should take
Sep 9, 2026
c2ba506
Promote the output contract from an agent field to a canvas node
Sep 9, 2026
4bc713d
Capture what the Output Parser extracted, and say so when it could not
Sep 9, 2026
b76cc2a
Say why a ReAct loop can't call the agent wired next to it
Sep 9, 2026
902138e
Revert "Say why a ReAct loop can't call the agent wired next to it"
Sep 9, 2026
b50516e
Preview a field reference as that field, not as a hole
Sep 9, 2026
482f928
Make the output shape one switch and one node
Sep 10, 2026
98d0bb6
Shorten the missing-parser prompt to the action it asks for
Sep 10, 2026
12bbe24
Ask the agent for its contracted values, not a second model to find them
Sep 10, 2026
87419d0
Treat a parser that names no field as no parser at all
Sep 10, 2026
523dff2
Deliver a predecessor's output on the edge, with no prose attached
Sep 10, 2026
573fc10
Show a node's prompt in labels on the canvas, not raw ids
Sep 10, 2026
99a0d50
Put back a Run button for a canvas that has no cells
Sep 10, 2026
f3c58f8
Let a declared metric claim the promoted key it only differs from by …
Sep 10, 2026
47ec5ef
Name a Script factor's cells after the script, not a hash
Sep 10, 2026
a019c71
Say which prompt format an experiment uses only when it isn't the cur…
Sep 10, 2026
6ffb597
Remove the prompt-contract mechanism; one prompt format for everything
Sep 10, 2026
37523e0
Let a metric declaration name its extraction key outright
Sep 10, 2026
8745d8b
Mark the two conversation strategies beta, and name the canvas transc…
Sep 10, 2026
8d340ec
Depend on the Motoro release that has the peer-agent port
Sep 10, 2026
3cf6e5e
Give ASAREE a CI workflow of its own
Sep 10, 2026
a6fc4ab
Give CI the Postgres the suite actually needs
Sep 10, 2026
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
150 changes: 150 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: CI

on:
push:
branches: [main]
pull_request:
branches: [main]

concurrency:
group: ci-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}

jobs:
backend:
name: Lint & Test (backend)
runs-on: ubuntu-latest

# The suite runs against a real Postgres, not sqlite or mocks: this
# codebase stores JSONB and never mocks the database (see the docstring
# on tests/test_protocols.py). pgvector's image rather than plain
# postgres because core's own schema declares a vector column.
services:
postgres:
image: pgvector/pgvector:pg16
env:
POSTGRES_USER: agentic
POSTGRES_PASSWORD: agentic
POSTGRES_DB: motoro
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U agentic"
--health-interval 5s
--health-timeout 5s
--health-retries 5

# Two databases on one server, matching the split in config.py:
# `ASAREE_DATABASE_URL` is core's schema, `ASAREE_PRODUCT_DATABASE_URL`
# is ASAREE's own tables. Bare `DATABASE_URL` is here for the sake of
# `python -m motoro.migrations`, which reads settings with no product
# prefix. Set as real env vars because CI has no .env to fall back on,
# and subprocess-spawned MCP servers inherit them.
env:
DATABASE_URL: postgresql+asyncpg://agentic:agentic@localhost:5432/motoro
ASAREE_DATABASE_URL: postgresql+asyncpg://agentic:agentic@localhost:5432/motoro
ASAREE_PRODUCT_DATABASE_URL: postgresql+asyncpg://agentic:agentic@localhost:5432/asaree

steps:
- uses: actions/checkout@v6

# No setup-python step on purpose: `requires-python = ">=3.12"` plus uv's
# own managed interpreters already decide the version, and pinning one
# here would only let CI drift from what `uv sync` gives a developer
# locally.
- uses: astral-sh/setup-uv@v7
with:
enable-cache: true

# Cheap, and it guards the exact class of bug this workflow was added
# after: a `tool.uv.sources` pin edited without re-locking, so the
# lockfile still resolves the old commit.
- name: Check lockfile is up to date
run: uv lock --check

# The slow step by a wide margin -- motoro depends unconditionally on
# sentence-transformers, which pulls ~4.5 GB of torch + CUDA that
# nothing in ASAREE imports (EpistasisLab/motoro#6). The uv cache above
# absorbs it on warm runs; when that issue lands, this drops to well
# under a minute cold.
- name: Install dependencies
run: uv sync --frozen --group dev

- name: Ruff check
run: uv run ruff check src/asaree tests

# Deliberately absent: `ruff format --check`. This repo is knowingly not
# ruff-format-clean and reformatting it is not wanted, so the step could
# only ever be permanently-yellow noise. `ruff check` above is the part
# that finds real defects.

# Non-blocking until the pre-existing strict-mode backlog is paid down
# (same reasoning as motoro's own CI). Flip to blocking once clean.
- name: Mypy (warn only)
continue-on-error: true
run: uv run mypy src/asaree

# The service container only creates POSTGRES_DB; the second database
# has to be made by hand. Done with asyncpg rather than psql so the job
# depends only on the locked dependencies, not on whatever client
# tooling the runner image happens to ship.
- name: Create ASAREE's product database
run: |
uv run python - <<'PY'
import asyncio, asyncpg

async def main() -> None:
conn = await asyncpg.connect("postgresql://agentic:agentic@localhost:5432/motoro")
try:
await conn.execute("CREATE DATABASE asaree")
finally:
await conn.close()

asyncio.run(main())
PY

# Core's chain first, then ASAREE's -- the order `asaree.migrations`
# documents. `deploy` rather than `upgrade` so the pattern registry is
# projected into architectural_patterns too; the schema alone is not
# enough for tests that resolve a coordination strategy.
- name: Migrate core schema
run: uv run python -m motoro.migrations deploy --url "$DATABASE_URL"

- name: Migrate ASAREE schema
run: uv run python -m asaree.migrations upgrade --url "$ASAREE_PRODUCT_DATABASE_URL"

# The whole suite, on purpose: 769 tests in ~13s locally, so there is no
# slow tier worth splitting out.
- name: Pytest
run: uv run pytest tests/ -q --tb=short

frontend:
name: Type Check & Lint (frontend)
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend

steps:
- uses: actions/checkout@v6

- uses: actions/setup-node@v4
with:
node-version: "22"
cache: npm
cache-dependency-path: frontend/package-lock.json

- name: Install dependencies
run: npm ci

# `tsc -b`, not `tsc --noEmit`: this is a project-references build
# (tsconfig.json -> tsconfig.app.json + tsconfig.node.json), and
# `--noEmit` against the solution file type-checks nothing at all.
- name: Type check
run: npx tsc -b

# oxlint exits 0 on warnings, so this reports rather than gates. The
# standing baseline is 9 warnings; it is here to make that number
# visible and stop it climbing unnoticed.
- name: Lint
run: npx oxlint src
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,3 @@ backups/
# Generated by hatch-vcs at build time from the git tag (see pyproject.toml).
src/asaree/_version.py
sdk/src/asaree_client/_version.py
prompts/
54 changes: 54 additions & 0 deletions compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Run the stack against the working trees instead of the built image.
#
# For testing a change to ASAREE *and* Motoro together before Motoro is tagged:
# pyproject.toml pins motoro to a released git tag on purpose (never a floating
# branch), so the only way to exercise an untagged Motoro is to put its source
# where the installed package sits. That is exactly what this file does -- no
# image rebuild, no throwaway tag, and nothing about the pin changes.
#
# Explicitly opt in, so it can never be active without being asked for:
#
# docker compose -f compose.yml -f compose.dev.yml up -d --force-recreate \
# asaree-migrate asaree-app asaree-worker
#
# and drop back to the released pin by leaving the -f off and recreating.
#
# The frontend needs nothing here: compose.yml already bind-mounts ./frontend
# for hot reload, so the UI is live off the working tree either way.

services:
# Included because a source change can bring a new Alembic revision with it,
# and this one-shot runs from the image like everything else.
asaree-migrate:
volumes:
- ./src:/app/src
- ${MOTORO_SRC:-../Motoro/src/motoro}:/app/.venv/lib/python3.13/site-packages/motoro:ro
- ./workspace-core/src/asaree_workspace_core:/app/workspace-core/src/asaree_workspace_core:ro

asaree-app:
volumes:
- ./data:/app/data
- ./src:/app/src
# Read-only: the container runs as root, so a writable mount would leave
# root-owned __pycache__ directories scattered through the host checkout.
# Python simply skips writing bytecode when it can't.
#
# This is a plain directory overlay rather than an editable install --
# safe only while Motoro's own dependencies are unchanged, since uv never
# sees it. If Motoro gains a dependency, rebuild against a tag instead.
# The bundled MCP servers already spawn with `uv run --no-sync`, so
# nothing at runtime tries to reconcile the venv behind this mount.
- ${MOTORO_SRC:-../Motoro/src/motoro}:/app/.venv/lib/python3.13/site-packages/motoro:ro
# Same idea for the workspace package. Unlike `src`, it is COPYed into
# the image and installed editable *inside* it, so a new module there (a
# stage plan, say) is invisible until the image is rebuilt. Overlaying the
# package directory rather than the project keeps the editable install's
# own recorded path valid.
- ./workspace-core/src/asaree_workspace_core:/app/workspace-core/src/asaree_workspace_core:ro

asaree-worker:
volumes:
- ./data:/app/data
- ./src:/app/src
- ${MOTORO_SRC:-../Motoro/src/motoro}:/app/.venv/lib/python3.13/site-packages/motoro:ro
- ./workspace-core/src/asaree_workspace_core:/app/workspace-core/src/asaree_workspace_core:ro
12 changes: 11 additions & 1 deletion frontend/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import type { DesignImpact, DesignRevision, DesignSpec, Experiment, ExperimentRe
import type { LLMConnectionCheck, LLMProvider, LLMSetting, LLMSettingModelsResponse } from '@/types/llmSettings'
import type { McpServer } from '@/types/mcpServers'
import type { OkfBundle, OkfDocument } from '@/types/okf'
import type { CellRunBatch, Protocol, ProtocolGraph, ProtocolRevision, ProtocolRun } from '@/types/protocols'
import type { CellRunBatch, PromptPreview, Protocol, ProtocolGraph, ProtocolRevision, ProtocolRun } from '@/types/protocols'
import type { Run, RunStep } from '@/types/runs'
import type { Skill, SkillListResponse, SkillUrlPreview } from '@/types/skills'

Expand Down Expand Up @@ -284,6 +284,12 @@ export const protocolsApi = {
// runnable Agent (see validate_single_node_runnable). Same polling shape
// as a plain run (getRun), just with node_runs carrying only this one key.
runNode: (id: string, nodeId: string) => request<ProtocolRun>(`/protocols/${id}/nodes/${nodeId}/run`, { method: 'POST' }),
// Read-only despite the POST: the graph goes in the body because the canvas
// being previewed is the one on screen, including edits autosave hasn't
// flushed yet. Creates no run of any kind. 422 for a node that isn't an
// agent, since only an agent is ever given a prompt.
promptPreview: (id: string, nodeId: string, graph: ProtocolGraph) =>
request<PromptPreview>(`/protocols/${id}/nodes/${nodeId}/prompt-preview`, { method: 'POST', body: { graph } }),
getRevision: (id: string, revisionId: string) => request<ProtocolRevision>(`/protocols/${id}/revisions/${revisionId}`),
getRun: (id: string, runId: string) => request<ProtocolRun>(`/protocols/${id}/runs/${runId}`),
// Only raises cancel_requested_at -- a no-op (200, unchanged row) once the
Expand Down Expand Up @@ -363,6 +369,10 @@ export const runsApi = {
// No server-side experiment_id filter exists yet (runs.py only filters by
// agent_id) -- callers filter client-side on run_metadata.experiment_id.
list: () => request<Run[]>('/runs'),
// Owner-scoped, same as the list. Fetched per node run for `input` -- the
// exact assembled prompt that agent was given, which is the one piece of
// handoff evidence that isn't already on the polled node_runs blob.
get: (runId: string) => request<Run>(`/runs/${runId}`),
getSteps: (runId: string) => request<RunStep[]>(`/runs/${runId}/steps`),
}

Expand Down
8 changes: 7 additions & 1 deletion frontend/src/components/protocol/AddNodePanel.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { ArrowRight, Atom, BookMarked, Bot, BrainCircuit, Cloud, Code2, Database, FileText, HardDrive, Repeat2, Route, ScrollText, ShieldCheck, Server, Sparkles, X } from 'lucide-react'
import { ArrowRight, Atom, BookMarked, Bot, Braces, BrainCircuit, Cloud, Code2, Database, FileText, HardDrive, Repeat2, Route, ScrollText, ShieldCheck, Server, Sparkles, X } from 'lucide-react'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { DATASET_BROWSE } from './datasetCatalog'
Expand Down Expand Up @@ -123,6 +123,12 @@ const NODE_CATALOG = [
description: 'Upload a single Markdown concept an Agent reads and rewrites as it works',
icon: FileText,
},
{
type: 'output_parser',
label: 'Output Parser',
description: "Defines the format an Agent's answer must take, and reads its named, typed fields back out",
icon: Braces,
},
{
type: 'script',
label: 'Script',
Expand Down
Loading
Loading