Skip to content

Commit 6349dca

Browse files
authored
Merge pull request #1 from nullclaw/feat/orchestration
Store API, orchestration fields, and per-state concurrency
2 parents 882a290 + 5595f8d commit 6349dca

16 files changed

Lines changed: 1573 additions & 721 deletions

File tree

AGENTS.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@ Primary modules:
1818
- `src/store.zig` - SQLite access, transactions, migrations, ownership/free helpers
1919
- `src/domain.zig` - pipeline FSM parse/validation/transition logic
2020
- `src/ids.zig` - UUID/token/hash/time helpers
21+
- `src/config.zig` - config loading and resolution
22+
- `src/export_manifest.zig` - nullhub manifest export
23+
- `src/from_json.zig` - JSON config bootstrap
2124
- `src/migrations/001_init.sql` - schema
25+
- `src/migrations/003_store.sql` - KV store table
26+
- `src/migrations/004_store_fts.sql` - FTS5 search index
2227

2328
Baseline commands:
2429

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 nullclaw contributors
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ stages, and attach artifacts.
1919
- `nullTickets` (this repository) is responsible for durable task state:
2020
- pipelines, stages, transitions
2121
- runs, leases, events, artifacts
22-
- dependencies, quality gates, assignments
22+
- dependencies, assignments
2323
- idempotent writes and optimistic transition checks
2424
- `nullTickets` is intentionally orchestration-light:
2525
- it does not decide global scheduling strategy
@@ -40,7 +40,7 @@ Practical architecture:
4040
You do not have to use all three components.
4141

4242
- `nullclaw` + `nullTickets` is a valid setup for sequential execution.
43-
- `nullTickets` can be used with other agent runtimes as long as they implement the tracker contract (`claim -> events/gates -> transition/fail`).
43+
- `nullTickets` can be used with other agent runtimes as long as they implement the tracker contract (`claim -> events -> transition/fail`).
4444
- `nullboiler` is optional and is mainly needed for advanced multi-agent orchestration.
4545

4646
## Adoption Path
@@ -87,10 +87,15 @@ bash tests/test_e2e.sh
8787

8888
- `src/main.zig` - process entrypoint, argument parsing, socket accept loop
8989
- `src/api.zig` - HTTP routing, request validation, response serialization
90-
- `src/store.zig` - SQLite queries, transactions, ownership/free helpers
90+
- `src/store.zig` - SQLite queries, transactions, migrations, ownership/free helpers
9191
- `src/domain.zig` - pipeline FSM parsing and validation
9292
- `src/ids.zig` - UUID/token/hash/time helpers
93+
- `src/config.zig` - config loading and resolution
94+
- `src/export_manifest.zig` - nullhub manifest export
95+
- `src/from_json.zig` - JSON config bootstrap
9396
- `src/migrations/001_init.sql` - database schema
97+
- `src/migrations/003_store.sql` - KV store table
98+
- `src/migrations/004_store_fts.sql` - FTS5 full-text search index
9499
- `tests/test_e2e.sh` - end-to-end API flow
95100

96101
## API Surface
@@ -116,15 +121,26 @@ bash tests/test_e2e.sh
116121
| `DELETE` | `/tasks/{id}/assignments/{agent_id}` | Unassign task |
117122
| `POST` | `/leases/claim` | Claim next task by role |
118123
| `POST` | `/leases/{id}/heartbeat` | Extend lease |
124+
| `GET` | `/tasks/{id}/run-state` | Get task run_id |
119125
| `POST` | `/runs/{id}/events` | Append run event |
120126
| `GET` | `/runs/{id}/events?limit=&cursor=` | List run events (cursor paginated) |
121-
| `POST` | `/runs/{id}/gates` | Add quality gate result |
122-
| `GET` | `/runs/{id}/gates` | List quality gate results |
123127
| `POST` | `/runs/{id}/transition` | Move task to next stage |
124128
| `POST` | `/runs/{id}/fail` | Mark run as failed |
125129
| `POST` | `/artifacts` | Attach artifact |
126130
| `GET` | `/artifacts?task_id=&run_id=&limit=&cursor=` | List artifacts (cursor paginated) |
127131
| `GET` | `/ops/queue` | Per-role queue stats for orchestrator |
132+
| `PUT` | `/store/{namespace}/{key}` | Put KV store entry |
133+
| `GET` | `/store/{namespace}/{key}` | Get KV store entry |
134+
| `DELETE` | `/store/{namespace}/{key}` | Delete KV store entry |
135+
| `GET` | `/store/{namespace}` | List entries in namespace |
136+
| `DELETE` | `/store/{namespace}` | Delete all entries in namespace |
137+
| `GET` | `/store/search?q=&namespace=&limit=&filter_path=&filter_value=` | Full-text search store |
138+
139+
### Store API Notes
140+
141+
- Store path segments are URL-decoded by the server. Clients should percent-encode reserved characters in `namespace` and `key` (for example spaces or `/`).
142+
- The namespace name `search` is reserved for `GET /store/search` and cannot be listed via `GET /store/{namespace}`.
143+
- `GET /store/search` also supports exact JSON filtering with `filter_path` and `filter_value` in addition to FTS search.
128144

129145
## Agent Loop
130146

@@ -134,7 +150,6 @@ POST /leases/claim { agent_id, agent_role, lease_ttl_ms? }
134150
-> 204 (no work)
135151
136152
POST /runs/{run_id}/events (Bearer <lease_token>)
137-
POST /runs/{run_id}/gates (Bearer <lease_token>)
138153
POST /runs/{run_id}/transition (Bearer <lease_token>)
139154
POST /runs/{run_id}/fail (Bearer <lease_token>)
140155

deps/sqlite/build.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub fn build(b: *std.Build) void {
1414
});
1515
lib.root_module.addCSourceFile(.{
1616
.file = b.path("sqlite3.c"),
17+
.flags = &.{"-DSQLITE_ENABLE_FTS5"},
1718
});
1819
lib.installHeader(b.path("sqlite3.h"), "sqlite3.h");
1920
lib.installHeader(b.path("sqlite3ext.h"), "sqlite3ext.h");

docs/api.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,6 @@ OTLP attribute mapping keys:
3232
- `GET /pipelines`
3333
- `GET /pipelines/{id}`
3434

35-
Pipeline transitions support `required_gates`:
36-
37-
```json
38-
{ "from": "coding", "to": "review", "trigger": "complete", "required_gates": ["tests_passed"] }
39-
```
40-
4135
## Tasks
4236

4337
- `POST /tasks`
@@ -71,8 +65,6 @@ Pipeline transitions support `required_gates`:
7165

7266
- `POST /runs/{id}/events` (Bearer)
7367
- `GET /runs/{id}/events?limit=&cursor=`
74-
- `POST /runs/{id}/gates` (Bearer)
75-
- `GET /runs/{id}/gates`
7668
- `POST /runs/{id}/transition` (Bearer)
7769
- `POST /runs/{id}/fail` (Bearer)
7870

@@ -86,7 +78,6 @@ Pipeline transitions support `required_gates`:
8678

8779
Transition returns `409` when:
8880

89-
- required gates are not passed
9081
- `expected_stage` does not match
9182
- `expected_task_version` does not match
9283

@@ -95,6 +86,21 @@ Transition returns `409` when:
9586
- `POST /artifacts`
9687
- `GET /artifacts?task_id=&run_id=&limit=&cursor=`
9788

89+
## Store (KV)
90+
91+
- `PUT /store/{namespace}/{key}` with `{ "value": ... }`
92+
- `GET /store/{namespace}/{key}`
93+
- `DELETE /store/{namespace}/{key}`
94+
- `GET /store/{namespace}` (list entries)
95+
- `DELETE /store/{namespace}` (delete all entries in namespace)
96+
- `GET /store/search?q=&namespace=&limit=&filter_path=&filter_value=` (FTS5 full-text search)
97+
98+
Notes:
99+
100+
- `namespace` and `key` path segments are URL-decoded server-side, so clients should percent-encode reserved characters such as spaces or `/`.
101+
- `search` is a reserved namespace name because `GET /store/search` is the full-text search endpoint.
102+
- `filter_path` and `filter_value` apply an exact JSON filter on top of FTS results.
103+
98104
## Ops
99105

100106
- `GET /ops/queue?near_expiry_ms=&stuck_ms=`

docs/architecture.md

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ In scope:
1818
- Lease ownership and retries
1919
- Run events and artifacts
2020
- Task dependencies (DAG)
21-
- Quality gate evidence and enforcement
2221
- Optional task assignments
22+
- Key-value store with full-text search
2323
- Orchestrator-facing queue metrics (`/ops/queue`)
2424

2525
Out of scope:
@@ -44,26 +44,24 @@ Out of scope:
4444
- `events`: append-only run timeline
4545
- `artifacts`: task/run outputs
4646
- `task_dependencies`: DAG edges (`task -> depends_on_task`)
47-
- `gate_results`: pass/fail evidence per run and gate
4847
- `task_assignments`: optional explicit owner binding for agents
48+
- `store`: namespaced key-value entries with FTS5 search
4949
- `idempotency_keys`: deduplication store for write retries
5050

5151
## Execution Semantics
5252

5353
1. Agent claims work using role (`/leases/claim`).
5454
2. Service starts a new run and grants a lease token.
5555
3. Agent sends events and periodic heartbeats.
56-
4. Agent (or orchestrator) may submit gate results for run quality checks.
57-
5. Agent either transitions the run to the next stage or fails it.
58-
6. Transition can require gate pass state and optimistic checks (`expected_stage`, `expected_task_version`).
59-
7. Failures apply retry policy and optional dead-letter routing.
60-
8. Lease is released on transition/failure or expires automatically.
56+
4. Agent either transitions the run to the next stage or fails it.
57+
5. Transition can enforce optimistic checks (`expected_stage`, `expected_task_version`).
58+
6. Failures apply retry policy and optional dead-letter routing.
59+
7. Lease is released on transition/failure or expires automatically.
6160

6261
## Safety and Correctness
6362

6463
- State-changing paths use SQL transactions (`BEGIN IMMEDIATE`) to avoid double-claim races.
6564
- Lease tokens are stored as SHA-256 hashes, not plaintext.
6665
- Pipeline transitions are validated against declared FSM definitions.
67-
- Required quality gates are enforced server-side on transition.
6866
- Claim excludes blocked dependencies, non-eligible retries, and foreign assignments.
6967
- API string fields are JSON-escaped at serialization time.

docs/workflows.md

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,6 @@ State metadata supports:
1919
- `description`: optional description
2020
- `terminal`: whether this stage is terminal
2121

22-
Transition metadata supports:
23-
24-
- `required_gates`: quality gates that must be passed before transition
25-
2622
## Transition Rules
2723

2824
- Every transition must reference existing states.
@@ -53,13 +49,6 @@ Transition metadata supports:
5349

5450
Dependencies are resolved only when the upstream task reaches a terminal pipeline stage.
5551

56-
## Quality Gates
57-
58-
- Add gate result: `POST /runs/{id}/gates`
59-
- Inspect gate history: `GET /runs/{id}/gates`
60-
61-
`/runs/{id}/transition` returns `409` if required gates are not passed.
62-
6352
## Assignments (Optional)
6453

6554
- Assign: `POST /tasks/{id}/assignments`

0 commit comments

Comments
 (0)