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
108 changes: 108 additions & 0 deletions cmd/codeaf/appworkstreams.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package main

import "github.com/Agent-Field/codeaf/internal/appwire"

var _ appwire.Workstreamer = (*appBackend)(nil)

// WP-1 makes this true when the store lands.
func (b *appBackend) ServesWorkstreams() bool { return false }

func (b *appBackend) WorkstreamList(appwire.WorkstreamListParams) (appwire.WorkstreamListResult, error) {
return appwire.WorkstreamListResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamGet(appwire.WorkstreamGetParams) (appwire.WorkstreamGetResult, error) {
return appwire.WorkstreamGetResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamCreate(appwire.WorkstreamCreateParams) (appwire.WorkstreamCreateResult, error) {
return appwire.WorkstreamCreateResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamUpdate(appwire.WorkstreamUpdateParams) (appwire.WorkstreamUpdateResult, error) {
return appwire.WorkstreamUpdateResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamMarkDone(appwire.WorkstreamMarkDoneParams) (appwire.WorkstreamMarkDoneResult, error) {
return appwire.WorkstreamMarkDoneResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamReopen(appwire.WorkstreamReopenParams) (appwire.WorkstreamReopenResult, error) {
return appwire.WorkstreamReopenResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPlanGet(appwire.WorkstreamPlanGetParams) (appwire.WorkstreamPlanGetResult, error) {
return appwire.WorkstreamPlanGetResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPlanSet(appwire.WorkstreamPlanSetParams) (appwire.WorkstreamPlanSetResult, error) {
return appwire.WorkstreamPlanSetResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageList(appwire.WorkstreamPageListParams) (appwire.WorkstreamPageListResult, error) {
return appwire.WorkstreamPageListResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageGet(appwire.WorkstreamPageGetParams) (appwire.WorkstreamPageGetResult, error) {
return appwire.WorkstreamPageGetResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageSave(appwire.WorkstreamPageSaveParams) (appwire.WorkstreamPageSaveResult, error) {
return appwire.WorkstreamPageSaveResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageCreate(appwire.WorkstreamPageCreateParams) (appwire.WorkstreamPageCreateResult, error) {
return appwire.WorkstreamPageCreateResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageUpdate(appwire.WorkstreamPageUpdateParams) (appwire.WorkstreamPageUpdateResult, error) {
return appwire.WorkstreamPageUpdateResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageMove(appwire.WorkstreamPageMoveParams) (appwire.WorkstreamPageMoveResult, error) {
return appwire.WorkstreamPageMoveResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageDelete(appwire.WorkstreamPageDeleteParams) (appwire.WorkstreamPageDeleteResult, error) {
return appwire.WorkstreamPageDeleteResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageHistory(appwire.WorkstreamPageHistoryParams) (appwire.WorkstreamPageHistoryResult, error) {
return appwire.WorkstreamPageHistoryResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamPageRestore(appwire.WorkstreamPageRestoreParams) (appwire.WorkstreamPageRestoreResult, error) {
return appwire.WorkstreamPageRestoreResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamRecordList(appwire.WorkstreamRecordListParams) (appwire.WorkstreamRecordListResult, error) {
return appwire.WorkstreamRecordListResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamRecordPropose(appwire.WorkstreamRecordProposeParams) (appwire.WorkstreamRecordProposeResult, error) {
return appwire.WorkstreamRecordProposeResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamRecordKeep(appwire.WorkstreamRecordKeepParams) (appwire.WorkstreamRecordKeepResult, error) {
return appwire.WorkstreamRecordKeepResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamRecordDrop(appwire.WorkstreamRecordDropParams) (appwire.WorkstreamRecordDropResult, error) {
return appwire.WorkstreamRecordDropResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamRecordSupersede(appwire.WorkstreamRecordSupersedeParams) (appwire.WorkstreamRecordSupersedeResult, error) {
return appwire.WorkstreamRecordSupersedeResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamGoAheadList(appwire.WorkstreamGoAheadListParams) (appwire.WorkstreamGoAheadListResult, error) {
return appwire.WorkstreamGoAheadListResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamGoAheadGrant(appwire.WorkstreamGoAheadGrantParams) (appwire.WorkstreamGoAheadGrantResult, error) {
return appwire.WorkstreamGoAheadGrantResult{}, appwire.ErrWorkstreamsNotYet
}

func (b *appBackend) WorkstreamGoAheadRevoke(appwire.WorkstreamGoAheadRevokeParams) (appwire.WorkstreamGoAheadRevokeResult, error) {
return appwire.WorkstreamGoAheadRevokeResult{}, appwire.ErrWorkstreamsNotYet
}
142 changes: 142 additions & 0 deletions cmd/codeaf/appworkstreams_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package main

import (
"errors"
"testing"

"github.com/Agent-Field/codeaf/internal/appwire"
)

// V4: the real backend implements every contract stub without touching its fields.
func TestAppWorkstreamsAreNotYetServed(t *testing.T) {
var backend appwire.Workstreamer = &appBackend{}
if backend.ServesWorkstreams() {
t.Fatal("the stub backend claims workstreams")
}
calls := []struct {
name string
call func() error
}{
{"workstream.list", func() error {
_, err := backend.WorkstreamList(appwire.WorkstreamListParams{})
return err
}},
{"workstream.get", func() error {
_, err := backend.WorkstreamGet(appwire.WorkstreamGetParams{})
return err
}},
{"workstream.create", func() error {
_, err := backend.WorkstreamCreate(appwire.WorkstreamCreateParams{})
return err
}},
{"workstream.update", func() error {
_, err := backend.WorkstreamUpdate(appwire.WorkstreamUpdateParams{})
return err
}},
{"workstream.mark_done", func() error {
_, err := backend.WorkstreamMarkDone(appwire.WorkstreamMarkDoneParams{})
return err
}},
{"workstream.reopen", func() error {
_, err := backend.WorkstreamReopen(appwire.WorkstreamReopenParams{})
return err
}},
{"workstream.plan_get", func() error {
_, err := backend.WorkstreamPlanGet(appwire.WorkstreamPlanGetParams{})
return err
}},
{"workstream.plan_set", func() error {
_, err := backend.WorkstreamPlanSet(appwire.WorkstreamPlanSetParams{})
return err
}},
{"page.list", func() error {
_, err := backend.WorkstreamPageList(appwire.WorkstreamPageListParams{})
return err
}},
{"page.get", func() error {
_, err := backend.WorkstreamPageGet(appwire.WorkstreamPageGetParams{})
return err
}},
{"page.save", func() error {
_, err := backend.WorkstreamPageSave(appwire.WorkstreamPageSaveParams{})
return err
}},
{"page.create", func() error {
_, err := backend.WorkstreamPageCreate(appwire.WorkstreamPageCreateParams{})
return err
}},
{"page.update", func() error {
_, err := backend.WorkstreamPageUpdate(appwire.WorkstreamPageUpdateParams{})
return err
}},
{"page.move", func() error {
_, err := backend.WorkstreamPageMove(appwire.WorkstreamPageMoveParams{})
return err
}},
{"page.delete", func() error {
_, err := backend.WorkstreamPageDelete(appwire.WorkstreamPageDeleteParams{})
return err
}},
{"page.history", func() error {
_, err := backend.WorkstreamPageHistory(appwire.WorkstreamPageHistoryParams{})
return err
}},
{"page.restore", func() error {
_, err := backend.WorkstreamPageRestore(appwire.WorkstreamPageRestoreParams{})
return err
}},
{"record.list", func() error {
_, err := backend.WorkstreamRecordList(appwire.WorkstreamRecordListParams{})
return err
}},
{"record.propose", func() error {
_, err := backend.WorkstreamRecordPropose(appwire.WorkstreamRecordProposeParams{})
return err
}},
{"record.keep", func() error {
_, err := backend.WorkstreamRecordKeep(appwire.WorkstreamRecordKeepParams{})
return err
}},
{"record.drop", func() error {
_, err := backend.WorkstreamRecordDrop(appwire.WorkstreamRecordDropParams{})
return err
}},
{"record.supersede", func() error {
_, err := backend.WorkstreamRecordSupersede(appwire.WorkstreamRecordSupersedeParams{})
return err
}},
{"goahead.list", func() error {
_, err := backend.WorkstreamGoAheadList(appwire.WorkstreamGoAheadListParams{})
return err
}},
{"goahead.grant", func() error {
_, err := backend.WorkstreamGoAheadGrant(appwire.WorkstreamGoAheadGrantParams{})
return err
}},
{"goahead.revoke", func() error {
_, err := backend.WorkstreamGoAheadRevoke(appwire.WorkstreamGoAheadRevokeParams{})
return err
}},
}
if len(calls) != len(appwire.WorkstreamMethods) {
t.Fatal("stub test does not cover every contract method")
}
for i, call := range calls {
if call.name != appwire.WorkstreamMethods[i] {
t.Fatalf("stub test index differs at %s", call.name)
}
t.Run(call.name, func(t *testing.T) {
err := call.call()
var wireErr *appwire.Error
if !errors.As(err, &wireErr) {
t.Fatal("stub must return an appwire.Error")
}
if wireErr.Code != appwire.CodeUnknownMethod {
t.Errorf("code = %s, want unknown_method", wireErr.Code)
}
if wireErr.Message != "workstreams: not implemented yet" {
t.Errorf("message = %s", wireErr.Message)
}
})
}
}
10 changes: 10 additions & 0 deletions docs/changes/unreleased/2263-app-workstreams-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
kind: added
title: codeaf app registers the workstream methods (contract only; they answer not implemented yet)
pr: 2263
surface: [app, docs]
invalidates: []
---

The 25 workstream, page, record and go-ahead methods and `workstream.changed` exist on the wire with their types, answering `unknown_method` with `workstreams: not implemented yet`.
Hello carries no `workstreams` feature until the store lands.
56 changes: 56 additions & 0 deletions docs/design/app-v3/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,14 @@ continue to apply; no method, parameter or reply field is added.
| `files.read` | `{agent, limit, offset, path}` | `FileReadResult` |
| `files.stat` | `{agent, paths}` | `FilesResult` |

## goahead methods

| method | params | result |
|---|---|---|
| `goahead.grant` | `in.WorkstreamGoAheadGrantParams` | `WorkstreamGoAheadGrantResult` |
| `goahead.list` | `in.WorkstreamGoAheadListParams` | `WorkstreamGoAheadListResult` |
| `goahead.revoke` | `in.WorkstreamGoAheadRevokeParams` | `WorkstreamGoAheadRevokeResult` |

## harness methods

| method | params | result |
Expand Down Expand Up @@ -942,6 +950,20 @@ $0.10 goes out as `0.1`.
| `orders.runs` | `{id, limit}` | `RunsResult` |
| `orders.stop` | `in.orderAt` | `OrderResult` |

## page methods

| method | params | result |
|---|---|---|
| `page.create` | `in.WorkstreamPageCreateParams` | `WorkstreamPageCreateResult` |
| `page.delete` | `in.WorkstreamPageDeleteParams` | `WorkstreamPageDeleteResult` |
| `page.get` | `in.WorkstreamPageGetParams` | `WorkstreamPageGetResult` |
| `page.history` | `in.WorkstreamPageHistoryParams` | `WorkstreamPageHistoryResult` |
| `page.list` | `in.WorkstreamPageListParams` | `WorkstreamPageListResult` |
| `page.move` | `in.WorkstreamPageMoveParams` | `WorkstreamPageMoveResult` |
| `page.restore` | `in.WorkstreamPageRestoreParams` | `WorkstreamPageRestoreResult` |
| `page.save` | `in.WorkstreamPageSaveParams` | `WorkstreamPageSaveResult` |
| `page.update` | `in.WorkstreamPageUpdateParams` | `WorkstreamPageUpdateResult` |

## ping methods

| method | params | result |
Expand Down Expand Up @@ -1003,6 +1025,16 @@ $0.10 goes out as `0.1`.
| `quick.promote` | `in.IDParams` | `taskResult` |
| `quick.undo` | `in.IDParams` | `Empty` |

## record methods

| method | params | result |
|---|---|---|
| `record.drop` | `in.WorkstreamRecordDropParams` | `WorkstreamRecordDropResult` |
| `record.keep` | `in.WorkstreamRecordKeepParams` | `WorkstreamRecordKeepResult` |
| `record.list` | `in.WorkstreamRecordListParams` | `WorkstreamRecordListResult` |
| `record.propose` | `in.WorkstreamRecordProposeParams` | `WorkstreamRecordProposeResult` |
| `record.supersede` | `in.WorkstreamRecordSupersedeParams` | `WorkstreamRecordSupersedeResult` |

## render methods

| method | params | result |
Expand Down Expand Up @@ -1207,6 +1239,19 @@ role pins and checked as the other pins are.
|---|---|---|
| `work.graph` | `in.GraphRequest` | `GraphResult` |

## workstream methods

| method | params | result |
|---|---|---|
| `workstream.create` | `in.WorkstreamCreateParams` | `WorkstreamCreateResult` |
| `workstream.get` | `in.WorkstreamGetParams` | `WorkstreamGetResult` |
| `workstream.list` | `in.WorkstreamListParams` | `WorkstreamListResult` |
| `workstream.mark_done` | `in.WorkstreamMarkDoneParams` | `WorkstreamMarkDoneResult` |
| `workstream.plan_get` | `in.WorkstreamPlanGetParams` | `WorkstreamPlanGetResult` |
| `workstream.plan_set` | `in.WorkstreamPlanSetParams` | `WorkstreamPlanSetResult` |
| `workstream.reopen` | `in.WorkstreamReopenParams` | `WorkstreamReopenResult` |
| `workstream.update` | `in.WorkstreamUpdateParams` | `WorkstreamUpdateResult` |

## Events

| event | data |
Expand Down Expand Up @@ -1263,6 +1308,7 @@ role pins and checked as the other pins are.
| `turn.thinking` | `TurnThinking` |
| `turn.thinking_done` | `TurnThinkingDone` |
| `turn.tool` | `TurnTool` |
| `workstream.changed` | `WorkstreamChanged` |

## Slash catalogue commands and forms

Expand Down Expand Up @@ -1445,6 +1491,16 @@ the state file), so on upgrade every agent starts read, and a new agent starts
read because it has said nothing. Engines that serve it announce
`agents.mark_seen` in `hello.features`.

## Workstreams (v0 proposal)

The Workstreams plan, WP-0, registers 25 methods and `workstream.changed` with their wire types.
The `workstreams` feature is announced only by an engine that serves them.
This build does not announce it and answers `unknown_method` with
`workstreams: not implemented yet`; the store lands in WP-1.
A workstream is a collection, sharing its id, naming rules and membership methods.
Its jobs use `factory_job` refs with `factory`; a job in another workstream is refused
as `in_other_workstream`. Versioned page and plan writes refuse stale bases with `version_conflict`.

## Errands (`agents.errand`)

An errand is one short, detached, metered model call with the app's own prompt
Expand Down
1 change: 1 addition & 0 deletions internal/appwire/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func (s *Server) Serve(ctx context.Context, in io.Reader, hello Hello, engine En
hello.Contract = Contract
hello = withFeature(hello, FeatureAgentEffortRungs)
hello = withCollectionsFeature(hello, engine)
hello = withWorkstreamsFeature(hello, engine)
hello = withMessageReadsFeature(hello, engine)
hello = withWorkGraphFeature(hello, engine)
hello = withSlashCatalogFeature(hello, engine)
Expand Down
Loading
Loading