mirror of
https://github.com/boshu2/agentops.git
synced 2026-09-14 15:08:13 +08:00
feat(cli): add software-factory operator lane
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
|
||||
### Every session starts where the last one left off.
|
||||
|
||||
Validation, memory, and lifecycle gates for coding agents — plain files, zero infrastructure, zero telemetry, and knowledge that compounds across every session.
|
||||
Validation, memory, lifecycle gates, and briefing-first control for coding agents. AgentOps acts as a software-factory control plane: bounded context goes in, validated code and durable learning come out.
|
||||
|
||||
[Start Here](#start-here) · [Install](#install) · [See It Work](#see-it-work) · [Skills](#skills) · [CLI](#the-ao-cli) · [FAQ](#faq) · [Newcomer Guide](docs/newcomer-guide.md)
|
||||
|
||||
@@ -29,6 +29,15 @@ Session 1, your agent spends 2 hours debugging a timeout bug. Session 15, a new
|
||||
|
||||
Every skill, hook, and CLI command exists to deliver one of these three. They form a single [lifecycle contract](docs/context-lifecycle.md), not separate features.
|
||||
|
||||
Operationally, that means AgentOps behaves like a software factory:
|
||||
|
||||
- briefings and startup context prepare the work order
|
||||
- RPI runs the delivery line
|
||||
- validation gates accept or reject output
|
||||
- the flywheel turns completed work into future advantage
|
||||
|
||||
See [Software Factory Surface](docs/software-factory.md) for the explicit operator lane.
|
||||
|
||||
| Capability | What you get |
|
||||
|-----|---------------|
|
||||
| Judgment validation | `/pre-mortem` challenges your plan before build; `/vibe` + `/council` validate code before commit |
|
||||
@@ -100,6 +109,16 @@ When you're ready for more:
|
||||
|
||||
Every skill works alone. Compose them however you want. Full catalog: [Skills](#skills).
|
||||
|
||||
If you want the explicit operator lane instead of individual primitives:
|
||||
|
||||
```bash
|
||||
ao factory start --goal "fix auth startup"
|
||||
/rpi "fix auth startup" # or: ao rpi phased "fix auth startup"
|
||||
ao codex stop
|
||||
```
|
||||
|
||||
That path keeps briefing, runtime startup, delivery, and loop closure on one surface. See [Software Factory Surface](docs/software-factory.md).
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
@@ -114,6 +133,12 @@ Each phase delivers one or more of the three capabilities — judgment, learning
|
||||
|
||||
`/rpi` orchestrates all three phases. `/evolve` keeps running `/rpi` against `GOALS.md` so the worst fitness gap gets addressed next. The output is code + state + memory + gates.
|
||||
|
||||
The explicit CLI operator surface around that line is:
|
||||
|
||||
- `ao factory start` for briefing-first startup
|
||||
- `/rpi` or `ao rpi phased` for delivery
|
||||
- `ao codex stop` for explicit loop closure
|
||||
|
||||
| Pattern | Chain | When |
|
||||
|---------|-------|------|
|
||||
| **Quick fix** | `/implement` | One issue, clear scope |
|
||||
|
||||
+48
-72
@@ -1,6 +1,26 @@
|
||||
# ao - Agent Operations CLI
|
||||
# ao - AgentOps Software Factory CLI
|
||||
|
||||
CLI for the CASS (Contextual Agent Session Search) knowledge flywheel. Automates knowledge capture, retrieval, and reinforcement learning across Claude Code sessions.
|
||||
`ao` is the explicit operator surface for AgentOps when you want the repo to
|
||||
behave like a software factory instead of a loose collection of agent
|
||||
primitives.
|
||||
|
||||
The short version:
|
||||
|
||||
```bash
|
||||
ao quick-start
|
||||
ao factory start --goal "fix auth startup"
|
||||
ao rpi phased "fix auth startup"
|
||||
ao codex stop
|
||||
```
|
||||
|
||||
That lane keeps four concerns explicit:
|
||||
|
||||
1. `ao factory start` surfaces a bounded work order by compiling a goal-time
|
||||
briefing when the corpus supports it, then running Codex startup.
|
||||
2. `ao rpi phased` runs the delivery line with fresh context per phase.
|
||||
3. `ao rpi status` lets the operator inspect long-running factory work.
|
||||
4. `ao codex stop` closes the flywheel so the session leaves behind learnings,
|
||||
citations, and handoff state.
|
||||
|
||||
## Install
|
||||
|
||||
@@ -11,85 +31,41 @@ go install github.com/boshu2/agentops/cli/cmd/ao@latest
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# From your repo root: create `.agents/` + register hooks
|
||||
ao init --hooks
|
||||
# From your repo root: create .agents/, starter knowledge surfaces, and hooks
|
||||
ao quick-start
|
||||
|
||||
# Verify installation
|
||||
ao hooks test
|
||||
# Start a goal with briefing-first runtime context
|
||||
ao factory start --goal "fix auth startup"
|
||||
|
||||
# Run the delivery lane
|
||||
ao rpi phased "fix auth startup"
|
||||
```
|
||||
|
||||
That's it. Knowledge now flows automatically between sessions.
|
||||
If you prefer the skill-first path, use `/rpi "fix auth startup"` after
|
||||
`ao factory start`.
|
||||
|
||||
## What It Does
|
||||
|
||||
**SessionStart**: Injects relevant prior knowledge weighted by freshness and utility.
|
||||
|
||||
**SessionEnd**: Extracts learnings and updates the feedback loop.
|
||||
|
||||
## Core Commands
|
||||
## Operator Surfaces
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `ao inject` | Inject knowledge into current session |
|
||||
| `ao forge transcript` | Extract learnings from session transcripts |
|
||||
| `ao feedback-loop` | Update utility scores based on outcomes |
|
||||
| `ao metrics report` | View flywheel health and escape velocity |
|
||||
| `ao factory start --goal "<goal>"` | Compile briefing-first startup context, then run explicit Codex start |
|
||||
| `ao knowledge brief --goal "<goal>"` | Build the task-time briefing directly |
|
||||
| `ao codex start` | Lower-level hookless Codex startup |
|
||||
| `ao rpi phased "<goal>"` | CLI-first Discovery -> Implementation -> Validation lane |
|
||||
| `ao rpi status` | Monitor long-running phased work |
|
||||
| `ao codex stop` | Close the loop explicitly at session end |
|
||||
|
||||
## Ratchet Workflow
|
||||
## Lower-Level Substrate
|
||||
|
||||
Track progress through the RPI (Research → Plan → Implement) workflow:
|
||||
These commands remain important, but they sit below the factory lane:
|
||||
|
||||
```bash
|
||||
ao ratchet gate plan # Check if ready for planning
|
||||
ao ratchet record research # Lock research completion
|
||||
ao ratchet status # View current progress
|
||||
```
|
||||
- `ao knowledge` for belief/playbook/briefing refresh and gap reporting
|
||||
- `ao context assemble` for a five-section task briefing
|
||||
- `ao lookup` and `ao search` for direct retrieval
|
||||
- `ao forge transcript` and `ao flywheel close-loop` for manual lifecycle work
|
||||
|
||||
## Hooks Management
|
||||
## Reference
|
||||
|
||||
```bash
|
||||
ao init --hooks # Recommended: repo setup + hooks (SessionStart + Stop)
|
||||
ao init --hooks --full # Optional: all 8 lifecycle events
|
||||
|
||||
# Lower-level (hooks only; does not create `.agents/`)
|
||||
ao hooks init # Generate hooks configuration
|
||||
ao hooks install # Install to Claude Code
|
||||
ao hooks show # View current hooks
|
||||
ao hooks test # Verify hooks work
|
||||
```
|
||||
|
||||
## Knowledge Commands
|
||||
|
||||
```bash
|
||||
ao lookup --query "kubernetes" # Look up knowledge about k8s
|
||||
ao search "error handling" # Search knowledge base
|
||||
```
|
||||
|
||||
## Task Integration
|
||||
|
||||
Sync Claude Code tasks with CASS maturity system:
|
||||
|
||||
```bash
|
||||
ao task-sync # Sync current tasks
|
||||
ao task-sync --promote # Promote completed tasks
|
||||
ao task-status # View maturity distribution
|
||||
```
|
||||
|
||||
## The Science
|
||||
|
||||
The flywheel equation:
|
||||
|
||||
```
|
||||
dK/dt = I(t) - δ·K + σ·ρ·K
|
||||
```
|
||||
|
||||
- **δ = 0.17/week** - Knowledge decay rate (Darr et al.)
|
||||
- **σρ > δ/100** - Operational escape velocity for compounding knowledge
|
||||
|
||||
See [docs/HOOKS.md](docs/HOOKS.md) for details.
|
||||
|
||||
For a complete command reference, see [docs/COMMANDS.md](docs/COMMANDS.md).
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
- [Software Factory Surface](../docs/software-factory.md)
|
||||
- [Session Lifecycle](../docs/workflows/session-lifecycle.md)
|
||||
- [CLI Reference](docs/COMMANDS.md)
|
||||
|
||||
@@ -57,6 +57,9 @@ func executeCommand(args ...string) (string, error) {
|
||||
origCodexStartLimit := codexStartLimit
|
||||
origCodexStartQuery := codexStartQuery
|
||||
origCodexStartNoMaintenance := codexStartNoMaintenance
|
||||
origFactoryStartGoal := factoryStartGoal
|
||||
origFactoryStartLimit := factoryStartLimit
|
||||
origFactoryStartNoMaintenance := factoryStartNoMaintenance
|
||||
origCodexStopSessionID := codexStopSessionID
|
||||
origCodexStopTranscriptPath := codexStopTranscriptPath
|
||||
origCodexStopAutoExtract := codexStopAutoExtract
|
||||
@@ -101,6 +104,9 @@ func executeCommand(args ...string) (string, error) {
|
||||
codexStartLimit = origCodexStartLimit
|
||||
codexStartQuery = origCodexStartQuery
|
||||
codexStartNoMaintenance = origCodexStartNoMaintenance
|
||||
factoryStartGoal = origFactoryStartGoal
|
||||
factoryStartLimit = origFactoryStartLimit
|
||||
factoryStartNoMaintenance = origFactoryStartNoMaintenance
|
||||
codexStopSessionID = origCodexStopSessionID
|
||||
codexStopTranscriptPath = origCodexStopTranscriptPath
|
||||
codexStopAutoExtract = origCodexStopAutoExtract
|
||||
@@ -232,7 +238,7 @@ func TestCobraCommandTreeRegistration(t *testing.T) {
|
||||
expectedCmds := []string{
|
||||
"anti-patterns", "autodev", "badge", "batch-feedback", "completion", "config",
|
||||
"constraint", "context", "codex", "contradict", "curate", "dedup",
|
||||
"defrag", "demo", "doctor", "extract", "feedback", "feedback-loop",
|
||||
"defrag", "demo", "doctor", "extract", "factory", "feedback", "feedback-loop",
|
||||
"findings", "flywheel", "forge", "gate", "goals", "handoff", "harvest", "hooks",
|
||||
"index", "init", "inject", "knowledge", "lookup", "maturity",
|
||||
"memory", "metrics", "migrate", "mind", "mine", "notebook", "plans",
|
||||
@@ -254,6 +260,7 @@ func TestCobraCommandTreeRegistration(t *testing.T) {
|
||||
// Verify parent commands have subcommands
|
||||
parentExpectations := map[string][]string{
|
||||
"autodev": {"init", "validate", "show"},
|
||||
"factory": {"start"},
|
||||
"goals": {"validate", "measure", "drift"},
|
||||
"knowledge": {"activate", "beliefs", "playbooks", "brief", "gaps"},
|
||||
"ratchet": {"status", "check", "next"},
|
||||
@@ -286,7 +293,7 @@ func TestCobraExpectedCmdsMatchRegistration(t *testing.T) {
|
||||
expectedCmds := []string{
|
||||
"anti-patterns", "autodev", "badge", "batch-feedback", "completion", "config",
|
||||
"constraint", "context", "codex", "contradict", "curate", "dedup",
|
||||
"defrag", "demo", "doctor", "extract", "feedback", "feedback-loop",
|
||||
"defrag", "demo", "doctor", "extract", "factory", "feedback", "feedback-loop",
|
||||
"findings", "flywheel", "forge", "gate", "goals", "handoff", "harvest", "hooks",
|
||||
"index", "init", "inject", "knowledge", "lookup", "maturity",
|
||||
"memory", "metrics", "migrate", "mind", "mine", "notebook", "plans",
|
||||
|
||||
@@ -0,0 +1,176 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
factoryStartGoal string
|
||||
factoryStartLimit int
|
||||
factoryStartNoMaintenance bool
|
||||
)
|
||||
|
||||
type factoryStartResult struct {
|
||||
Workspace string `json:"workspace"`
|
||||
Goal string `json:"goal,omitempty"`
|
||||
Briefing string `json:"briefing,omitempty"`
|
||||
BriefingWarning string `json:"briefing_warning,omitempty"`
|
||||
Codex codexStartResult `json:"codex"`
|
||||
Recommended []string `json:"recommended,omitempty"`
|
||||
}
|
||||
|
||||
var factoryCmd = &cobra.Command{
|
||||
Use: "factory",
|
||||
Short: "Software-factory operator surface for briefing-first agent work",
|
||||
Long: `Software-factory operator surface for AgentOps.
|
||||
|
||||
This surface keeps the operator lane explicit:
|
||||
1. Compile a bounded goal-time briefing when the knowledge corpus can support it
|
||||
2. Start the runtime with explicit startup context
|
||||
3. Run the delivery lane through /rpi or ao rpi phased
|
||||
4. Close the loop with ao codex stop and ao knowledge activate
|
||||
|
||||
Use lower-level commands like ao knowledge, ao codex, ao context assemble, and
|
||||
ao rpi when you need substrate-level control.`,
|
||||
}
|
||||
|
||||
var factoryStartCmd = &cobra.Command{
|
||||
Use: "start",
|
||||
Short: "Compile a briefing when possible, then run explicit Codex startup",
|
||||
Long: `Start the software-factory operator lane for a concrete goal.
|
||||
|
||||
When --goal is provided, AgentOps attempts to build a goal-time briefing from
|
||||
the local .agents corpus before surfacing Codex startup context. The startup
|
||||
path then retrieves ranked briefings, learnings, patterns, findings, research,
|
||||
and next work for the same goal so the runtime starts from a bounded work
|
||||
packet instead of a broad recency dump.`,
|
||||
Args: cobra.NoArgs,
|
||||
RunE: runFactoryStart,
|
||||
}
|
||||
|
||||
func init() {
|
||||
factoryCmd.GroupID = "workflow"
|
||||
rootCmd.AddCommand(factoryCmd)
|
||||
factoryCmd.AddCommand(factoryStartCmd)
|
||||
|
||||
factoryStartCmd.Flags().StringVar(&factoryStartGoal, "goal", "", "Goal to brief and use as the startup query")
|
||||
factoryStartCmd.Flags().IntVar(&factoryStartLimit, "limit", 3, "Maximum artifacts to surface per category during startup")
|
||||
factoryStartCmd.Flags().BoolVar(&factoryStartNoMaintenance, "no-maintenance", false, "Skip safe close-loop maintenance on start")
|
||||
}
|
||||
|
||||
func runFactoryStart(cmd *cobra.Command, args []string) error {
|
||||
cwd, err := resolveProjectDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
result, err := performFactoryStart(cwd, strings.TrimSpace(factoryStartGoal), factoryStartLimit, factoryStartNoMaintenance)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return outputFactoryStartResult(result)
|
||||
}
|
||||
|
||||
func performFactoryStart(cwd, goal string, limit int, noMaintenance bool) (factoryStartResult, error) {
|
||||
result := factoryStartResult{
|
||||
Workspace: cwd,
|
||||
Goal: goal,
|
||||
Recommended: factoryRecommendedCommands(goal),
|
||||
}
|
||||
|
||||
if goal != "" {
|
||||
agentsRoot := filepath.Join(cwd, ".agents")
|
||||
run, err := runKnowledgeNativeBuilder(cwd, agentsRoot, knowledgeBuilderInvocation{
|
||||
Step: "briefing",
|
||||
Implementation: knowledgeBuilderImplementationAONative,
|
||||
Args: []string{"--goal", goal},
|
||||
})
|
||||
if err != nil {
|
||||
result.BriefingWarning = err.Error()
|
||||
} else {
|
||||
result.Briefing = firstNonEmptyTrimmed(run.Metadata["briefing"], run.Path)
|
||||
}
|
||||
}
|
||||
|
||||
origQuery := codexStartQuery
|
||||
origLimit := codexStartLimit
|
||||
origNoMaintenance := codexStartNoMaintenance
|
||||
codexStartQuery = goal
|
||||
codexStartLimit = limit
|
||||
codexStartNoMaintenance = noMaintenance
|
||||
defer func() {
|
||||
codexStartQuery = origQuery
|
||||
codexStartLimit = origLimit
|
||||
codexStartNoMaintenance = origNoMaintenance
|
||||
}()
|
||||
|
||||
codexResult, err := performCodexStart(cwd)
|
||||
if err != nil {
|
||||
return factoryStartResult{}, err
|
||||
}
|
||||
result.Codex = codexResult
|
||||
if result.Briefing == "" && len(codexResult.Briefings) > 0 {
|
||||
result.Briefing = codexResult.Briefings[0].Path
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func outputFactoryStartResult(result factoryStartResult) error {
|
||||
if GetOutput() == "json" {
|
||||
enc := json.NewEncoder(os.Stdout)
|
||||
enc.SetIndent("", " ")
|
||||
return enc.Encode(result)
|
||||
}
|
||||
|
||||
fmt.Println("Software Factory Start")
|
||||
fmt.Println("======================")
|
||||
fmt.Printf("Workspace: %s\n", result.Workspace)
|
||||
if result.Goal != "" {
|
||||
fmt.Printf("Goal: %s\n", result.Goal)
|
||||
}
|
||||
if result.Briefing != "" {
|
||||
fmt.Printf("Briefing: %s\n", result.Briefing)
|
||||
} else if result.Goal != "" {
|
||||
fmt.Println("Briefing: not built")
|
||||
} else {
|
||||
fmt.Println("Briefing: skipped (no --goal provided)")
|
||||
}
|
||||
if result.BriefingWarning != "" {
|
||||
fmt.Printf("Briefing note: %s\n", result.BriefingWarning)
|
||||
}
|
||||
fmt.Printf("Startup context: %s\n", result.Codex.StartupContextPath)
|
||||
if result.Codex.MemoryPath != "" {
|
||||
fmt.Printf("Memory: %s\n", result.Codex.MemoryPath)
|
||||
}
|
||||
fmt.Println()
|
||||
fmt.Println("Factory lane:")
|
||||
for _, step := range result.Recommended {
|
||||
fmt.Printf(" - %s\n", step)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func factoryRecommendedCommands(goal string) []string {
|
||||
if goal == "" {
|
||||
return []string{
|
||||
"Set a concrete goal, then run `ao factory start --goal \"your goal\"` for a briefing-first startup.",
|
||||
"Run `/rpi \"your goal\"` for the skill-first delivery lane, or `ao rpi phased \"your goal\"` for CLI-first phase isolation.",
|
||||
"Use `ao rpi status` to monitor long-running phased work.",
|
||||
"Run `ao codex stop` when the session ends so the flywheel closes explicitly.",
|
||||
}
|
||||
}
|
||||
|
||||
quotedGoal := fmt.Sprintf("%q", goal)
|
||||
return []string{
|
||||
fmt.Sprintf("Run `/rpi %s` for the skill-first software-factory lane.", quotedGoal),
|
||||
fmt.Sprintf("Or run `ao rpi phased %s` for CLI-first phase isolation.", quotedGoal),
|
||||
"Use `ao rpi status` to monitor long-running phased work.",
|
||||
"Run `ao codex stop` when the session ends so the flywheel closes explicitly.",
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestFactoryStartJSONBuildsBriefingThenRunsCodexStartup(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("CODEX_THREAD_ID", "019d1bf7-58ea-79e1-9f5d-02109d930081")
|
||||
t.Setenv("CODEX_INTERNAL_ORIGINATOR_OVERRIDE", "Codex Desktop")
|
||||
|
||||
indexPath := filepath.Join(home, ".codex", "session_index.jsonl")
|
||||
if err := os.MkdirAll(filepath.Dir(indexPath), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(indexPath, []byte(`{"id":"019d1bf7-58ea-79e1-9f5d-02109d930081","thread_name":"factory startup","updated_at":"2026-03-23T12:00:00Z"}`+"\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
repo := t.TempDir()
|
||||
writeKnowledgeCorpusFixtures(t, repo)
|
||||
|
||||
origProjectDir := testProjectDir
|
||||
testProjectDir = repo
|
||||
defer func() { testProjectDir = origProjectDir }()
|
||||
|
||||
out, err := executeCommand("factory", "start", "--json", "--no-maintenance", "--goal", "Healthy topic rollout")
|
||||
if err != nil {
|
||||
t.Fatalf("factory start --json: %v\noutput: %s", err, out)
|
||||
}
|
||||
|
||||
var result factoryStartResult
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &result); err != nil {
|
||||
t.Fatalf("parse factory start json: %v\noutput: %s", err, out)
|
||||
}
|
||||
|
||||
if result.Goal != "Healthy topic rollout" {
|
||||
t.Fatalf("goal = %q, want %q", result.Goal, "Healthy topic rollout")
|
||||
}
|
||||
if result.Briefing == "" || !knowledgePathExists(result.Briefing) {
|
||||
t.Fatalf("briefing missing: %q", result.Briefing)
|
||||
}
|
||||
if result.Codex.StartupContextPath == "" || !fileExists(result.Codex.StartupContextPath) {
|
||||
t.Fatalf("startup context missing: %q", result.Codex.StartupContextPath)
|
||||
}
|
||||
if len(result.Codex.Briefings) == 0 {
|
||||
t.Fatal("expected codex startup to surface the generated briefing")
|
||||
}
|
||||
if len(result.Recommended) == 0 {
|
||||
t.Fatal("expected recommended next steps")
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(result.Codex.StartupContextPath)
|
||||
if err != nil {
|
||||
t.Fatalf("read startup context: %v", err)
|
||||
}
|
||||
content := string(data)
|
||||
if !strings.Contains(content, "# Briefings") {
|
||||
t.Fatalf("startup context missing briefings section:\n%s", content)
|
||||
}
|
||||
if !strings.Contains(content, "Healthy topic rollout") {
|
||||
t.Fatalf("startup context missing goal-specific briefing:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFactoryStartJSONWarnsWhenBriefingCannotBeBuiltYet(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("CODEX_THREAD_ID", "019d1bf7-58ea-79e1-9f5d-02109d930082")
|
||||
t.Setenv("CODEX_INTERNAL_ORIGINATOR_OVERRIDE", "Codex Desktop")
|
||||
|
||||
indexPath := filepath.Join(home, ".codex", "session_index.jsonl")
|
||||
if err := os.MkdirAll(filepath.Dir(indexPath), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.WriteFile(indexPath, []byte(`{"id":"019d1bf7-58ea-79e1-9f5d-02109d930082","thread_name":"factory startup no corpus","updated_at":"2026-03-23T12:00:00Z"}`+"\n"), 0o644); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
repo := t.TempDir()
|
||||
if err := os.MkdirAll(filepath.Join(repo, ".agents"), 0o755); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
origProjectDir := testProjectDir
|
||||
testProjectDir = repo
|
||||
defer func() { testProjectDir = origProjectDir }()
|
||||
|
||||
out, err := executeCommand("factory", "start", "--json", "--no-maintenance", "--goal", "No topic packets yet")
|
||||
if err != nil {
|
||||
t.Fatalf("factory start without topic packets should still succeed: %v\noutput: %s", err, out)
|
||||
}
|
||||
|
||||
var result factoryStartResult
|
||||
if err := json.Unmarshal([]byte(strings.TrimSpace(out)), &result); err != nil {
|
||||
t.Fatalf("parse factory start json: %v\noutput: %s", err, out)
|
||||
}
|
||||
if result.Briefing != "" {
|
||||
t.Fatalf("briefing = %q, want empty when no topic packets exist", result.Briefing)
|
||||
}
|
||||
if result.BriefingWarning == "" {
|
||||
t.Fatal("expected briefing warning when no topic packets exist")
|
||||
}
|
||||
if result.Codex.StartupContextPath == "" || !fileExists(result.Codex.StartupContextPath) {
|
||||
t.Fatalf("startup context missing: %q", result.Codex.StartupContextPath)
|
||||
}
|
||||
}
|
||||
+21
-11
@@ -20,7 +20,7 @@ This command:
|
||||
1. Creates .agents/ directory structure
|
||||
2. Optionally initializes beads (git-native issues)
|
||||
3. Creates starter knowledge pack
|
||||
4. Shows next steps
|
||||
4. Shows the software-factory operator lane
|
||||
|
||||
Examples:
|
||||
ao quick-start # Full setup with beads
|
||||
@@ -86,6 +86,9 @@ func runQuickstart(cmd *cobra.Command, args []string) error {
|
||||
|
||||
fmt.Println("━━━ STEP 1: Creating .agents/ structure ━━━")
|
||||
dirs := []string{
|
||||
".agents/briefings",
|
||||
".agents/knowledge",
|
||||
".agents/playbooks",
|
||||
".agents/research",
|
||||
".agents/synthesis",
|
||||
".agents/specs",
|
||||
@@ -330,32 +333,39 @@ func showNextSteps(hasBeads bool) {
|
||||
`)
|
||||
|
||||
if hasBeads {
|
||||
fmt.Println(` 1. Create your first issue:
|
||||
fmt.Println(` 1. Start the software-factory lane for a concrete goal:
|
||||
$ ao factory start --goal "your first objective"
|
||||
|
||||
2. Create your first issue:
|
||||
$ bd create "My first task"
|
||||
|
||||
2. Start working:
|
||||
3. Start working:
|
||||
$ claude
|
||||
> /implement <issue-id>
|
||||
> /rpi "your first objective"
|
||||
|
||||
3. When done, extract learnings:
|
||||
> /post-mortem`)
|
||||
4. When done, close the loop explicitly:
|
||||
$ ao codex stop`)
|
||||
} else {
|
||||
fmt.Println(` 1. Start Claude in your project:
|
||||
fmt.Println(` 1. Start the software-factory lane for a concrete goal:
|
||||
$ ao factory start --goal "your first objective"
|
||||
|
||||
2. Start Claude in your project:
|
||||
$ claude
|
||||
|
||||
2. Work normally - learnings go to .agents/
|
||||
3. Work normally:
|
||||
> /rpi "your first objective"
|
||||
|
||||
3. When ready for full power:
|
||||
4. When ready for tracked execution:
|
||||
$ bd init
|
||||
$ bd create "My first task"`)
|
||||
}
|
||||
|
||||
fmt.Print(`
|
||||
4. Your knowledge compounds automatically via Smart Connections
|
||||
5. Your knowledge compounds through briefings, RPI, and flywheel closeout
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
|
||||
"Others forget. AgentOps learns. Zero infrastructure."
|
||||
"Stateful environment. Stateless agents. One explicit operator lane."
|
||||
|
||||
═══════════════════════════════════════════════════════════════════
|
||||
`)
|
||||
|
||||
@@ -95,6 +95,9 @@ func TestQuickstart_ShowNextSteps_WithBeads(t *testing.T) {
|
||||
if !strings.Contains(out, "bd") {
|
||||
t.Errorf("with beads=true, expected bd reference in output:\n%s", out)
|
||||
}
|
||||
if !strings.Contains(out, "ao factory start") {
|
||||
t.Errorf("with beads=true, expected factory lane in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuickstart_ShowNextSteps_WithoutBeads(t *testing.T) {
|
||||
@@ -102,6 +105,9 @@ func TestQuickstart_ShowNextSteps_WithoutBeads(t *testing.T) {
|
||||
if out == "" {
|
||||
t.Error("expected non-empty output for next steps")
|
||||
}
|
||||
if !strings.Contains(out, "ao factory start") {
|
||||
t.Errorf("without beads, expected factory lane in output:\n%s", out)
|
||||
}
|
||||
}
|
||||
|
||||
func TestQuickstart_CreateStarterPack_CreatesPatterns(t *testing.T) {
|
||||
|
||||
+7
-2
@@ -22,11 +22,16 @@ var (
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "ao",
|
||||
Short: "AgentOps Knowledge Compounding CLI",
|
||||
Long: `ao is the CLI for AgentOps, a knowledge compounding workflow system.
|
||||
Long: `ao is the CLI for AgentOps, a software-factory control plane for repo-native agent work.
|
||||
|
||||
"Problem in. Value out. Intelligence compounds."
|
||||
|
||||
The Knowledge Flywheel:
|
||||
Software Factory Lane:
|
||||
ao factory start --goal "fix auth startup"
|
||||
/rpi "fix auth startup" or ao rpi phased "fix auth startup"
|
||||
ao codex stop
|
||||
|
||||
The Knowledge Flywheel underneath it:
|
||||
Sessions compound via .agents/ + Smart Connections.
|
||||
Others start fresh. You get smarter every session.
|
||||
|
||||
|
||||
@@ -798,6 +798,35 @@ ao codex stop [flags]
|
||||
|
||||
---
|
||||
|
||||
### `ao factory`
|
||||
|
||||
Software-factory operator surface for AgentOps.
|
||||
|
||||
```
|
||||
ao factory [command]
|
||||
```
|
||||
|
||||
**Subcommands:**
|
||||
|
||||
#### `ao factory start`
|
||||
|
||||
Start the software-factory operator lane for a concrete goal.
|
||||
|
||||
```
|
||||
ao factory start [flags]
|
||||
```
|
||||
|
||||
**Flags:**
|
||||
|
||||
```
|
||||
--goal string Goal to brief and use as the startup query
|
||||
-h, --help help for start
|
||||
--limit int Maximum artifacts to surface per category during startup (default 3)
|
||||
--no-maintenance Skip safe close-loop maintenance on start
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### `ao feedback-loop`
|
||||
|
||||
Automatically close the MemRL feedback loop by updating utilities of cited learnings.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
## Architecture
|
||||
|
||||
- [How It Works](how-it-works.md) — Brownian Ratchet, Ralph Wiggum Pattern, agent backends, hooks, context windowing
|
||||
- [Software Factory Surface](software-factory.md) — Explicit operator surface connecting briefings, RPI lanes, and loop closure
|
||||
- [Architecture](ARCHITECTURE.md) — System design and component overview
|
||||
- [Architecture Folder Index](architecture/README.md) — Architecture subdocs overview
|
||||
- [Codex Hookless Lifecycle](architecture/codex-hookless-lifecycle.md) — Runtime-aware lifecycle fallback for Codex when hooks are unavailable
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
Parallel agents produce noisy output; councils filter it; ratchets lock progress so it can never regress.
|
||||
|
||||
Think of the mechanics below as the substrate under the software-factory
|
||||
operator surface: briefings and startup context prepare the work order, RPI
|
||||
phases run the delivery lane, and the flywheel closes the learning loop. See
|
||||
[Software Factory Surface](software-factory.md).
|
||||
|
||||
## The Three Gaps
|
||||
|
||||
AgentOps exists because most agent tooling leaves three gaps open after prompt construction and routing are solved. The runtime mechanics described on this page are organized around closing them:
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
# Software Factory Surface
|
||||
|
||||
AgentOps already contains the machinery for a software factory. This document
|
||||
names the operator surface explicitly so users do not have to infer it from
|
||||
skills, hooks, CLI commands, and internal artifacts.
|
||||
|
||||
## Thesis
|
||||
|
||||
AgentOps is best understood as a **software-factory control plane**.
|
||||
|
||||
The environment carries:
|
||||
|
||||
- bounded briefing and context assembly
|
||||
- tracked planning and scoped execution
|
||||
- validation gates and ratchet checkpoints
|
||||
- durable learning and loop closure
|
||||
- isolated work lanes for long-running or parallel work
|
||||
|
||||
The workers remain replaceable. The environment carries continuity.
|
||||
|
||||
This follows the repo's stateful-environment/stateless-agents theory and its
|
||||
own lifecycle/flywheel contracts: briefings and runtime state are the operator
|
||||
surface; packets, chunks, topics, and builders are substrate.
|
||||
|
||||
## Operator Lane
|
||||
|
||||
For Codex and other explicit-runtime workflows, treat this as the canonical
|
||||
lane:
|
||||
|
||||
```bash
|
||||
ao factory start --goal "fix auth startup"
|
||||
/rpi "fix auth startup"
|
||||
# or: ao rpi phased "fix auth startup"
|
||||
ao codex stop
|
||||
```
|
||||
|
||||
That lane does four things:
|
||||
|
||||
1. `ao factory start` tries to compile a small goal-time briefing, then runs
|
||||
explicit Codex startup so the session begins with bounded context.
|
||||
2. `/rpi` or `ao rpi phased` runs the delivery line: discovery,
|
||||
implementation, validation.
|
||||
3. `ao rpi status` lets the operator inspect long-running phased work.
|
||||
4. `ao codex stop` closes the flywheel explicitly so the session leaves behind
|
||||
learnings, citations, and handoff state.
|
||||
|
||||
## Surface Map
|
||||
|
||||
| Layer | Purpose | Primary surfaces |
|
||||
|------|---------|------------------|
|
||||
| Operator | What the human or lead agent should touch first | `ao factory start`, `/rpi`, `ao rpi phased`, `ao rpi status`, `ao codex stop` |
|
||||
| Briefing + runtime | Bounded startup context and thread-time state | `ao knowledge brief`, `ao codex start`, `ao codex ensure-start`, `ao context assemble` |
|
||||
| Delivery line | Research, planning, execution, validation | `/discovery`, `/plan`, `/crank`, `/validation`, `/rpi` |
|
||||
| Learning loop | Convert completed work into future advantage | `ao codex stop`, `ao knowledge activate`, `ao flywheel close-loop`, `/retro`, `/forge` |
|
||||
| Substrate | Retrieval, provenance, packetization, and promotion machinery | `.agents/packets/`, `.agents/topics/`, `.agents/briefings/`, `.agents/findings/`, hooks, builder logic |
|
||||
|
||||
## Why This Surface Exists
|
||||
|
||||
The factory framing matters because the repo already has the hard parts:
|
||||
|
||||
- RPI provides the conveyor belt.
|
||||
- Context packets and briefings provide bounded work orders.
|
||||
- The flywheel provides durable learning and loop closure.
|
||||
- Codex lifecycle commands provide explicit runtime boundaries where hooks do
|
||||
not exist.
|
||||
|
||||
Without an explicit operator lane, users see a powerful collection of
|
||||
primitives. With it, they see one product surface.
|
||||
|
||||
## Design Rules
|
||||
|
||||
- Prefer briefings over giant startup dumps.
|
||||
- Keep substrate and operator surfaces distinct.
|
||||
- Let external validation outrank self-report.
|
||||
- Treat thin topics as discovery-only until evidence improves.
|
||||
- Keep `athena` scoped to hygiene, not full operator-surface activation.
|
||||
|
||||
## Related Docs
|
||||
|
||||
- [How It Works](how-it-works.md)
|
||||
- [Context Packet](context-packet.md)
|
||||
- [Knowledge Flywheel](knowledge-flywheel.md)
|
||||
- [Session Lifecycle](workflows/session-lifecycle.md)
|
||||
- [CLI Reference](../cli/docs/COMMANDS.md)
|
||||
@@ -20,7 +20,31 @@ Just describe what you want:
|
||||
| "What should I work on?" | Shows status, suggests next task |
|
||||
| "Where was I?" | Shows last session, current state, blockers |
|
||||
|
||||
### Option 2: Codex Explicit Lifecycle (Recommended in Codex)
|
||||
### Option 2: Software Factory Lane (Recommended in Codex)
|
||||
|
||||
```bash
|
||||
# Start session with a goal-time briefing when possible
|
||||
ao factory start --goal "fix auth startup"
|
||||
|
||||
# Run the delivery lane
|
||||
/rpi "fix auth startup"
|
||||
# or: ao rpi phased "fix auth startup"
|
||||
|
||||
# Monitor long-running phased work
|
||||
ao rpi status
|
||||
|
||||
# End session
|
||||
ao codex stop
|
||||
|
||||
# Inspect lifecycle + flywheel health
|
||||
ao codex status
|
||||
```
|
||||
|
||||
`ao factory start` keeps the operator lane explicit: build a bounded briefing if
|
||||
the corpus can support it, run Codex startup, then move into RPI. The lower
|
||||
level lifecycle commands still exist when you want direct control.
|
||||
|
||||
### Option 3: Lower-Level Codex Lifecycle
|
||||
|
||||
```bash
|
||||
# Start session
|
||||
@@ -32,16 +56,13 @@ ao search "topic" --cite retrieved
|
||||
|
||||
# End session
|
||||
ao codex stop
|
||||
|
||||
# Inspect lifecycle + flywheel health
|
||||
ao codex status
|
||||
```
|
||||
|
||||
For normal Codex skill usage, entry skills drive the same startup path with
|
||||
`ao codex ensure-start`, and closeout-owner skills drive the same closeout path
|
||||
with `ao codex ensure-stop`.
|
||||
|
||||
### Option 3: Slash Commands (Hook-Capable Power Users)
|
||||
### Option 4: Slash Commands (Hook-Capable Power Users)
|
||||
|
||||
```bash
|
||||
# Start session
|
||||
@@ -54,7 +75,7 @@ with `ao codex ensure-stop`.
|
||||
/session-end
|
||||
```
|
||||
|
||||
**Use the mode your runtime actually supports.** Claude/OpenCode can drive lifecycle via hooks. Codex uses explicit `ao codex start` / `ao codex stop` / `ao codex status`, while Codex skills automate the same boundaries via `ao codex ensure-start` / `ao codex ensure-stop`.
|
||||
**Use the mode your runtime actually supports.** Claude/OpenCode can drive lifecycle via hooks. Codex users should usually prefer `ao factory start --goal "<goal>"` as the briefing-first operator surface, while `ao codex start` / `ao codex stop` / `ao codex status` remain the lower-level lifecycle primitives. Codex skills automate the same boundaries via `ao codex ensure-start` / `ao codex ensure-stop`.
|
||||
|
||||
---
|
||||
|
||||
@@ -63,7 +84,7 @@ with `ao codex ensure-stop`.
|
||||
| Mode | Start | Closeout | Notes |
|
||||
|------|-------|----------|-------|
|
||||
| Hook-capable | Natural language, `/session-start`, or startup hooks | Natural language, `/session-end`, or session-end hooks | Best fit for Claude/OpenCode when hooks are installed |
|
||||
| Codex hookless fallback | `ao codex start` or skill-driven `ao codex ensure-start` | `ao codex stop` or skill-driven `ao codex ensure-stop` | No startup/session-end hook surface under `~/.codex`; lifecycle is explicit |
|
||||
| Codex hookless fallback | `ao factory start --goal "<goal>"`, `ao codex start`, or skill-driven `ao codex ensure-start` | `ao codex stop` or skill-driven `ao codex ensure-stop` | No startup/session-end hook surface under `~/.codex`; lifecycle is explicit |
|
||||
| Manual fallback | `ao inject`, `ao lookup` | `ao forge transcript`, `ao flywheel close-loop` | Lowest-level portable path |
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user