481 lines
13 KiB
Markdown
481 lines
13 KiB
Markdown
# Main Agent Runtime Flow Implementation Plan
|
|
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
|
|
**Goal:** Add the first production-ready main-agent run layer so StoryForge can create confirmable runs, display runtime progress in the floating OneLiner window, and unify “handoff to main agent” actions.
|
|
|
|
**Architecture:** Keep existing `OneLiner session/message` as the conversation layer, add `agent_runs` plus `agent_run_events` inside `oneliner_features.py` as a separate runtime layer, then extend the existing floating OneLiner UI to render a run header area above the chat stream and wire selected page actions into run creation.
|
|
|
|
**Tech Stack:** FastAPI, SQLite, existing StoryForge Web V4 vanilla JS, Node test runner, Python unittest
|
|
|
|
---
|
|
|
|
### Task 1: Save docs for the runtime flow
|
|
|
|
**Files:**
|
|
- Create: `docs/superpowers/specs/2026-03-29-main-agent-runtime-flow-design.md`
|
|
- Create: `docs/superpowers/plans/2026-03-29-main-agent-runtime-flow.md`
|
|
|
|
- [ ] **Step 1: Save the approved design**
|
|
|
|
Write the runtime flow design into the spec file above.
|
|
|
|
- [ ] **Step 2: Save this implementation plan**
|
|
|
|
Write this plan file and keep it committed with the implementation.
|
|
|
|
- [ ] **Step 3: Commit docs checkpoint**
|
|
|
|
```bash
|
|
git add docs/superpowers/specs/2026-03-29-main-agent-runtime-flow-design.md docs/superpowers/plans/2026-03-29-main-agent-runtime-flow.md
|
|
git commit -m "docs: add main agent runtime flow spec"
|
|
```
|
|
|
|
### Task 2: Add failing backend tests for the run layer
|
|
|
|
**Files:**
|
|
- Modify: `tests/test_main_agent_governance.py`
|
|
|
|
- [ ] **Step 1: Write the failing test for run creation**
|
|
|
|
Add a test that creates a run through `POST /v2/oneliner/runs` and asserts:
|
|
|
|
```python
|
|
response = self.client.post(
|
|
"/v2/oneliner/runs",
|
|
headers=self.ctx["member_headers"],
|
|
json={
|
|
"project_id": self.ctx["project_id"],
|
|
"source_screen": "dashboard",
|
|
"source_action_key": "homepage-primary-action",
|
|
"title": "跟进重点账号",
|
|
"summary": "先由主 Agent 评估优先级",
|
|
"intent_key": "track_account",
|
|
"platform": "douyin",
|
|
"platform_scope": "single_platform",
|
|
"plan_request": {
|
|
"goal": "跟进重点账号",
|
|
"steps": ["读取当前项目上下文", "检查重点账号变化", "决定下一步"]
|
|
},
|
|
},
|
|
)
|
|
payload = response.json()
|
|
self.assertEqual(response.status_code, 200, response.text)
|
|
self.assertEqual(payload["run_status"], "needs_confirmation")
|
|
self.assertEqual(payload["source_screen"], "dashboard")
|
|
self.assertEqual(payload["platform"], "douyin")
|
|
self.assertTrue(payload["plan"]["steps"])
|
|
```
|
|
|
|
- [ ] **Step 2: Write the failing test for confirm flow**
|
|
|
|
Add a test that confirms a run and asserts:
|
|
|
|
```python
|
|
confirm = self.client.post(
|
|
f"/v2/oneliner/runs/{run_id}/confirm",
|
|
headers=self.ctx["member_headers"],
|
|
json={"reason": "user confirmed"},
|
|
)
|
|
payload = confirm.json()
|
|
self.assertEqual(confirm.status_code, 200, confirm.text)
|
|
self.assertIn(payload["run_status"], {"queued", "running"})
|
|
```
|
|
|
|
- [ ] **Step 3: Write the failing test for projectless governance snapshot**
|
|
|
|
Add a test that creates a run for an approved user with no project id and verifies no default project is auto-created while the run still stores a blank governance snapshot.
|
|
|
|
- [ ] **Step 4: Run the backend tests and confirm they fail**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
python3 -m unittest tests.test_main_agent_governance -v
|
|
```
|
|
|
|
Expected: FAIL because the run tables and endpoints do not exist yet.
|
|
|
|
### Task 3: Add backend run schema and payload helpers
|
|
|
|
**Files:**
|
|
- Modify: `collector-service/app/oneliner_features.py`
|
|
|
|
- [ ] **Step 1: Add the new tables**
|
|
|
|
Add schema creation for:
|
|
|
|
```sql
|
|
CREATE TABLE IF NOT EXISTS agent_runs (
|
|
id TEXT PRIMARY KEY,
|
|
session_id TEXT NOT NULL,
|
|
user_id TEXT NOT NULL,
|
|
project_id TEXT NOT NULL DEFAULT '',
|
|
source_type TEXT NOT NULL,
|
|
source_screen TEXT NOT NULL,
|
|
source_action_key TEXT NOT NULL,
|
|
title TEXT NOT NULL,
|
|
summary TEXT NOT NULL DEFAULT '',
|
|
intent_key TEXT NOT NULL DEFAULT '',
|
|
platform TEXT NOT NULL DEFAULT '',
|
|
platform_scope TEXT NOT NULL DEFAULT 'single_platform',
|
|
delivery_mode TEXT NOT NULL DEFAULT 'hybrid',
|
|
run_status TEXT NOT NULL,
|
|
scheduling_mode TEXT NOT NULL DEFAULT 'queued',
|
|
active_executor_key TEXT NOT NULL DEFAULT '',
|
|
plan_json TEXT NOT NULL DEFAULT '{}',
|
|
result_json TEXT NOT NULL DEFAULT '{}',
|
|
status_summary TEXT NOT NULL DEFAULT '',
|
|
needs_user_input INTEGER NOT NULL DEFAULT 0,
|
|
blocked_reason TEXT NOT NULL DEFAULT '',
|
|
active_admin_override_notice_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL,
|
|
started_at TEXT NOT NULL DEFAULT '',
|
|
finished_at TEXT NOT NULL DEFAULT ''
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS agent_run_events (
|
|
id TEXT PRIMARY KEY,
|
|
run_id TEXT NOT NULL,
|
|
event_type TEXT NOT NULL,
|
|
summary TEXT NOT NULL DEFAULT '',
|
|
details_json TEXT NOT NULL DEFAULT '{}',
|
|
created_at TEXT NOT NULL
|
|
);
|
|
```
|
|
|
|
- [ ] **Step 2: Add helper payload builders**
|
|
|
|
Implement focused helpers for:
|
|
|
|
```python
|
|
def _agent_run_payload(row: dict[str, Any]) -> dict[str, Any]:
|
|
...
|
|
|
|
def _agent_run_event_payload(row: dict[str, Any]) -> dict[str, Any]:
|
|
...
|
|
|
|
def _list_agent_run_events(run_id: str) -> list[dict[str, Any]]:
|
|
...
|
|
```
|
|
|
|
- [ ] **Step 3: Add governance snapshot helper**
|
|
|
|
Add a helper that returns:
|
|
|
|
```python
|
|
{
|
|
"project_id": project_id,
|
|
"platform": platform,
|
|
"effective_policy": ...,
|
|
"layers": ...,
|
|
"active_admin_override_notice": ...,
|
|
}
|
|
```
|
|
|
|
for use at run creation time.
|
|
|
|
- [ ] **Step 4: Run backend tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
python3 -m unittest tests.test_main_agent_governance -v
|
|
```
|
|
|
|
Expected: tests still fail, but schema-related failures move forward.
|
|
|
|
### Task 4: Add backend run endpoints and state transitions
|
|
|
|
**Files:**
|
|
- Modify: `collector-service/app/oneliner_features.py`
|
|
- Test: `tests/test_main_agent_governance.py`
|
|
|
|
- [ ] **Step 1: Add request models**
|
|
|
|
Add minimal request models for:
|
|
|
|
```python
|
|
class AgentRunCreateRequest(BaseModel):
|
|
project_id: str = ""
|
|
source_screen: str
|
|
source_action_key: str
|
|
title: str
|
|
summary: str = ""
|
|
intent_key: str = ""
|
|
platform: str = ""
|
|
platform_scope: str = "single_platform"
|
|
plan_request: dict[str, Any] = Field(default_factory=dict)
|
|
|
|
class AgentRunConfirmRequest(BaseModel):
|
|
reason: str = ""
|
|
|
|
class AgentRunCancelRequest(BaseModel):
|
|
reason: str = ""
|
|
```
|
|
|
|
- [ ] **Step 2: Add `POST /v2/oneliner/runs`**
|
|
|
|
Create a run that:
|
|
- resolves or creates the current session via the existing OneLiner session helper
|
|
- stores `run_status = "needs_confirmation"`
|
|
- snapshots current governance
|
|
- logs `run.created`
|
|
|
|
- [ ] **Step 3: Add read endpoints**
|
|
|
|
Implement:
|
|
|
|
```python
|
|
@app.get("/v2/oneliner/runs")
|
|
@app.get("/v2/oneliner/runs/{run_id}")
|
|
@app.get("/v2/oneliner/runs/{run_id}/events")
|
|
```
|
|
|
|
The list endpoint should return the latest 20 runs for the current user and project, newest first.
|
|
|
|
- [ ] **Step 4: Add confirm and cancel endpoints**
|
|
|
|
Implement:
|
|
|
|
```python
|
|
@app.post("/v2/oneliner/runs/{run_id}/confirm")
|
|
@app.post("/v2/oneliner/runs/{run_id}/cancel")
|
|
```
|
|
|
|
Confirm should:
|
|
- move `needs_confirmation -> queued`
|
|
- set `status_summary = "等待主 Agent 执行"` or `"主 Agent 正在执行"`
|
|
- create `run.confirmed`
|
|
- for the first version, immediately promote to `running` when no other active run exists for that user/project
|
|
|
|
Cancel should:
|
|
- move `needs_confirmation -> cancelled` or `queued -> cancelled`
|
|
- create `run.cancelled`
|
|
|
|
- [ ] **Step 5: Add one backend test for event sequencing**
|
|
|
|
Assert that creation + confirm writes at least:
|
|
- `run.created`
|
|
- `run.confirmed`
|
|
- one of `run.queued` / `run.started`
|
|
|
|
- [ ] **Step 6: Re-run backend tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
python3 -m unittest tests.test_main_agent_governance -v
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
### Task 5: Add failing frontend tests for the floating runtime header
|
|
|
|
**Files:**
|
|
- Modify: `web/storyforge-web-v4/tests/workbench-pages.test.mjs`
|
|
|
|
- [ ] **Step 1: Add a failing test for the run header**
|
|
|
|
Assert the OneLiner UI now includes a dedicated runtime header zone:
|
|
|
|
```javascript
|
|
const source = extractBetween(APP, "function ensureOneLinerUi()", "function renderOneLinerSessionTabs()");
|
|
assert.match(source, /data-role="oneliner-runs"/);
|
|
```
|
|
|
|
- [ ] **Step 2: Add a failing test for the run list loader**
|
|
|
|
Assert the app state loader hits the new endpoints:
|
|
|
|
```javascript
|
|
const source = extractBetween(APP, "async function loadAgentControlSurfaces(projectId = \"\")", "async function loadOneLinerMessages(sessionId)");
|
|
assert.match(source, /\/v2\/oneliner\/runs/);
|
|
```
|
|
|
|
- [ ] **Step 3: Add a failing test for the handoff entrypoint**
|
|
|
|
Assert the click handler routes a main-agent handoff to a dedicated action instead of only opening the chat:
|
|
|
|
```javascript
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
assert.match(actions, /handoff-to-main-agent/);
|
|
```
|
|
|
|
- [ ] **Step 4: Run the frontend tests and confirm failure**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test web/storyforge-web-v4/tests/workbench-pages.test.mjs
|
|
```
|
|
|
|
Expected: FAIL on missing run UI and handoff actions.
|
|
|
|
### Task 6: Extend app state and OneLiner UI with the runtime header
|
|
|
|
**Files:**
|
|
- Modify: `web/storyforge-web-v4/assets/app.js`
|
|
- Modify: `web/storyforge-web-v4/assets/styles.css`
|
|
- Test: `web/storyforge-web-v4/tests/workbench-pages.test.mjs`
|
|
|
|
- [ ] **Step 1: Extend app state**
|
|
|
|
Add state fields:
|
|
|
|
```javascript
|
|
onelinerRuns: [],
|
|
selectedOnelinerRunId: "",
|
|
onelinerRunEvents: [],
|
|
```
|
|
|
|
- [ ] **Step 2: Render the runtime header area**
|
|
|
|
Inside `ensureOneLinerUi()`, add a dedicated block:
|
|
|
|
```html
|
|
<div class="oneliner-runs" data-role="oneliner-runs"></div>
|
|
```
|
|
|
|
above the message list.
|
|
|
|
- [ ] **Step 3: Add render helpers**
|
|
|
|
Implement focused helpers:
|
|
|
|
```javascript
|
|
function pickPrimaryRun(runs) { ... }
|
|
function renderOneLinerRunSummary(primaryRun, runs) { ... }
|
|
function renderOneLinerRunEventChips(primaryRunEvents) { ... }
|
|
```
|
|
|
|
Behavior:
|
|
- prefer `needs_confirmation`
|
|
- then `blocked`
|
|
- then `running`
|
|
- then latest `done`
|
|
|
|
- [ ] **Step 4: Add CSS for the runtime header**
|
|
|
|
Add styles for:
|
|
- `.oneliner-runs`
|
|
- `.oneliner-run-card`
|
|
- `.oneliner-run-title`
|
|
- `.oneliner-run-summary`
|
|
|
|
Keep the current OneLiner visual language. Do not redesign the shell.
|
|
|
|
- [ ] **Step 5: Re-run frontend tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test web/storyforge-web-v4/tests/workbench-pages.test.mjs
|
|
node --check web/storyforge-web-v4/assets/app.js
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
### Task 7: Wire run loading, confirmation, and page handoff
|
|
|
|
**Files:**
|
|
- Modify: `web/storyforge-web-v4/assets/app.js`
|
|
- Test: `web/storyforge-web-v4/tests/workbench-pages.test.mjs`
|
|
|
|
- [ ] **Step 1: Load runs into app state**
|
|
|
|
Extend `loadAgentControlSurfaces(projectId)` to fetch:
|
|
|
|
```javascript
|
|
storyforgeFetch(`/v2/oneliner/runs?project_id=${encodeURIComponent(normalizedProjectId)}`)
|
|
```
|
|
|
|
and store it in `appState.onelinerRuns`.
|
|
|
|
- [ ] **Step 2: Add API helpers**
|
|
|
|
Implement:
|
|
|
|
```javascript
|
|
async function createMainAgentRun(input) { ... }
|
|
async function confirmMainAgentRun(runId, reason = "") { ... }
|
|
async function cancelMainAgentRun(runId, reason = "") { ... }
|
|
```
|
|
|
|
- [ ] **Step 3: Add the dedicated handoff action**
|
|
|
|
In the click handler, add:
|
|
|
|
```javascript
|
|
if (name === "handoff-to-main-agent") {
|
|
...
|
|
}
|
|
```
|
|
|
|
This action should:
|
|
- create a `needs_confirmation` run
|
|
- refresh run state
|
|
- open the OneLiner panel
|
|
|
|
- [ ] **Step 4: Replace the first batch of entrypoints**
|
|
|
|
Update these buttons to use `handoff-to-main-agent`:
|
|
- homepage primary action handoff
|
|
- homepage secondary action handoff
|
|
- strategy page `交给 OneLiner 调整`
|
|
- agent governance handoff tags
|
|
|
|
- [ ] **Step 5: Add confirmation controls in the run header**
|
|
|
|
For `needs_confirmation` runs, show:
|
|
- `确认执行`
|
|
- `取消`
|
|
|
|
For `running/queued/blocked/done`, show compact status only.
|
|
|
|
- [ ] **Step 6: Re-run frontend tests**
|
|
|
|
Run:
|
|
|
|
```bash
|
|
node --test web/storyforge-web-v4/tests/workbench-pages.test.mjs
|
|
```
|
|
|
|
Expected: PASS.
|
|
|
|
### Task 8: Full verification, deploy, and publish
|
|
|
|
**Files:**
|
|
- Modify only files already touched above
|
|
|
|
- [ ] **Step 1: Run full verification**
|
|
|
|
```bash
|
|
python3 -m unittest tests.test_main_agent_governance tests.test_platform_contracts tests.test_production_baseline -v
|
|
node --test web/storyforge-web-v4/tests/dashboard-home.test.mjs web/storyforge-web-v4/tests/workbench-pages.test.mjs
|
|
python3 -m compileall collector-service/app tests
|
|
git diff --check
|
|
bash scripts/check_repo_baseline.sh
|
|
```
|
|
|
|
Expected: all pass.
|
|
|
|
- [ ] **Step 2: Deploy to NAS**
|
|
|
|
```bash
|
|
STORYFORGE_FNOS_COLLECTOR_DEPLOY_MODE=remote_build bash /Users/kris/code/StoryForge-gitea/scripts/deploy_fnos_storyforge_collector.sh
|
|
bash /Users/kris/code/StoryForge-gitea/scripts/deploy_fnos_storyforge_web.sh
|
|
bash /Users/kris/code/StoryForge-gitea/scripts/smoke_fnos_storyforge_lan.sh
|
|
```
|
|
|
|
Expected: web, collector, cutvideo tunnel, compat smoke all pass.
|
|
|
|
- [ ] **Step 3: Commit and push**
|
|
|
|
```bash
|
|
git add collector-service/app/oneliner_features.py tests/test_main_agent_governance.py web/storyforge-web-v4/assets/app.js web/storyforge-web-v4/assets/styles.css web/storyforge-web-v4/tests/workbench-pages.test.mjs docs/superpowers/specs/2026-03-29-main-agent-runtime-flow-design.md docs/superpowers/plans/2026-03-29-main-agent-runtime-flow.md
|
|
git commit -m "feat: add main agent runtime flow"
|
|
git push gitea codex/storyforge-live-orchestrator-sync-20260323
|
|
```
|