@kontourai/flow-agents 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/runtime-compat.yml +1 -1
- package/CHANGELOG.md +23 -0
- package/README.md +38 -19
- package/build/src/cli/flow-kit.js +9 -4
- package/build/src/cli/runtime-adapter.js +9 -5
- package/build/src/cli/telemetry-doctor.js +4 -1
- package/build/src/runtime-adapters.js +34 -0
- package/build/src/tools/build-universal-bundles.js +18 -1
- package/console.telemetry.json +115 -20
- package/docs/_layouts/default.html +2 -0
- package/docs/index.md +8 -0
- package/docs/integrations/index.md +4 -0
- package/docs/integrations/knowledge-kit-live.md +211 -0
- package/docs/kit-authoring-guide.md +169 -0
- package/docs/spec/runtime-hook-surface.md +56 -3
- package/evals/acceptance/run.sh +10 -1
- package/evals/acceptance/test_knowledge_kit_live.sh +221 -0
- package/evals/acceptance/test_pi_harness.sh +15 -0
- package/evals/integration/test_runtime_adapter_activation.sh +113 -1
- package/integrations/strands/examples/knowledge_kit_live.py +461 -0
- package/integrations/strands/flow_agents_strands/steering.py +54 -1
- package/integrations/strands/tests/test_hooks.py +88 -0
- package/integrations/strands-ts/src/hooks.ts +104 -0
- package/integrations/strands-ts/test/test-steering.ts +159 -0
- package/kits/catalog.json +6 -0
- package/kits/knowledge/adapters/default-store/index.js +821 -0
- package/kits/knowledge/adapters/flow-runner/index.js +1179 -0
- package/kits/knowledge/adapters/flow-runner/telemetry.js +174 -0
- package/kits/knowledge/docs/README.md +135 -0
- package/kits/knowledge/docs/store-contract.md +526 -0
- package/kits/knowledge/evals/consolidation/suite.test.js +1234 -0
- package/kits/knowledge/evals/contract-suite/suite.test.js +670 -0
- package/kits/knowledge/evals/ingest-compile/suite.test.js +574 -0
- package/kits/knowledge/evals/synthesis/suite.test.js +909 -0
- package/kits/knowledge/flows/compile.flow.json +60 -0
- package/kits/knowledge/flows/consolidate.flow.json +77 -0
- package/kits/knowledge/flows/ingest.flow.json +60 -0
- package/kits/knowledge/flows/store-contract.flow.json +48 -0
- package/kits/knowledge/flows/synthesize.flow.json +77 -0
- package/kits/knowledge/kit.json +78 -0
- package/package.json +1 -1
- package/src/cli/flow-kit.ts +10 -4
- package/src/cli/runtime-adapter.ts +10 -5
- package/src/cli/telemetry-doctor.ts +4 -1
- package/src/runtime-adapters.ts +35 -0
- package/src/tools/build-universal-bundles.ts +18 -1
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "knowledge.compile",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "select-raws", "next": "compile" },
|
|
6
|
+
{ "id": "compile", "next": "link" },
|
|
7
|
+
{ "id": "link", "next": "done" },
|
|
8
|
+
{ "id": "done", "next": null }
|
|
9
|
+
],
|
|
10
|
+
"gates": {
|
|
11
|
+
"select-raws-gate": {
|
|
12
|
+
"step": "select-raws",
|
|
13
|
+
"expects": [
|
|
14
|
+
{
|
|
15
|
+
"id": "raw-ids-selected",
|
|
16
|
+
"kind": "surface.claim",
|
|
17
|
+
"required": true,
|
|
18
|
+
"description": "One or more raw record IDs have been selected for compilation. Each ID must resolve to an existing raw record in the store.",
|
|
19
|
+
"claim": {
|
|
20
|
+
"type": "knowledge.compile.selection",
|
|
21
|
+
"subject": "artifact",
|
|
22
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"compile-gate": {
|
|
28
|
+
"step": "compile",
|
|
29
|
+
"expects": [
|
|
30
|
+
{
|
|
31
|
+
"id": "compiled-record-provenance",
|
|
32
|
+
"kind": "surface.claim",
|
|
33
|
+
"required": true,
|
|
34
|
+
"description": "The compiled record has been created with provenance.source_ids listing every consumed raw ID, and source links (kind='source') from the compiled record to each raw record — satisfying the store contract's compiled record requirements.",
|
|
35
|
+
"claim": {
|
|
36
|
+
"type": "knowledge.compile.provenance",
|
|
37
|
+
"subject": "artifact",
|
|
38
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"link-gate": {
|
|
44
|
+
"step": "link",
|
|
45
|
+
"expects": [
|
|
46
|
+
{
|
|
47
|
+
"id": "provenance-refs-resolve",
|
|
48
|
+
"kind": "surface.claim",
|
|
49
|
+
"required": true,
|
|
50
|
+
"description": "Every provenance ref in the compiled record resolves to a retrievable raw record via the store's get() operation. The graph index reflects all source links.",
|
|
51
|
+
"claim": {
|
|
52
|
+
"type": "knowledge.compile.link-integrity",
|
|
53
|
+
"subject": "artifact",
|
|
54
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "knowledge.consolidate",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "related-event", "next": "propose" },
|
|
6
|
+
{ "id": "propose", "next": "evidence-gate" },
|
|
7
|
+
{ "id": "evidence-gate", "next": "apply-or-reject" },
|
|
8
|
+
{ "id": "apply-or-reject", "next": "done" },
|
|
9
|
+
{ "id": "done", "next": null }
|
|
10
|
+
],
|
|
11
|
+
"gates": {
|
|
12
|
+
"related-event-gate": {
|
|
13
|
+
"step": "related-event",
|
|
14
|
+
"expects": [
|
|
15
|
+
{
|
|
16
|
+
"id": "related-compiled-records-found",
|
|
17
|
+
"kind": "surface.claim",
|
|
18
|
+
"required": true,
|
|
19
|
+
"description": "One or more compiled records linked to the snapshot's topic have been identified. The trigger fires when new compiled records matching the snapshot topic (by category or explicit topic tag) are present. The result is a cluster of compiled record IDs that form the consolidation input.",
|
|
20
|
+
"claim": {
|
|
21
|
+
"type": "knowledge.consolidate.trigger",
|
|
22
|
+
"subject": "artifact",
|
|
23
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"propose-gate": {
|
|
29
|
+
"step": "propose",
|
|
30
|
+
"expects": [
|
|
31
|
+
{
|
|
32
|
+
"id": "consolidation-proposal-recorded",
|
|
33
|
+
"kind": "surface.claim",
|
|
34
|
+
"required": true,
|
|
35
|
+
"description": "A consolidation proposal has been recorded. The proposal carries: the updated snapshot body (latest decisions), source_ids referencing every compiled record that contributed, and supersedes_ids referencing any prior snapshot(s) for the same topic. The snapshot record is NOT mutated at this step — only a consolidation proposal record and proposes link are created.",
|
|
36
|
+
"claim": {
|
|
37
|
+
"type": "knowledge.consolidate.proposal",
|
|
38
|
+
"subject": "artifact",
|
|
39
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"evidence-gate": {
|
|
45
|
+
"step": "evidence-gate",
|
|
46
|
+
"expects": [
|
|
47
|
+
{
|
|
48
|
+
"id": "proposal-carries-source-refs",
|
|
49
|
+
"kind": "surface.claim",
|
|
50
|
+
"required": true,
|
|
51
|
+
"description": "The consolidation proposal evidence includes source_ids referencing every compiled record that contributed to the proposed snapshot body. Gate rejects if source_ids is empty or any referenced record does not exist. Also verifies that the proposer record has a 'proposes' link to the snapshot target.",
|
|
52
|
+
"claim": {
|
|
53
|
+
"type": "knowledge.consolidate.evidence",
|
|
54
|
+
"subject": "artifact",
|
|
55
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"apply-gate": {
|
|
61
|
+
"step": "apply-or-reject",
|
|
62
|
+
"expects": [
|
|
63
|
+
{
|
|
64
|
+
"id": "consolidation-gate-decision",
|
|
65
|
+
"kind": "surface.claim",
|
|
66
|
+
"required": true,
|
|
67
|
+
"description": "A gate decision (apply or reject) has been recorded. If applied: snapshot body is updated via the store update op with the proposed body; supersede op links the new snapshot to any prior snapshots for the same topic; all superseded snapshots remain queryable with provenance intact. If rejected: the snapshot body is byte-identical to its pre-proposal state.",
|
|
68
|
+
"claim": {
|
|
69
|
+
"type": "knowledge.consolidate.gate-decision",
|
|
70
|
+
"subject": "artifact",
|
|
71
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "knowledge.ingest",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "capture", "next": "classify" },
|
|
6
|
+
{ "id": "classify", "next": "route" },
|
|
7
|
+
{ "id": "route", "next": "done" },
|
|
8
|
+
{ "id": "done", "next": null }
|
|
9
|
+
],
|
|
10
|
+
"gates": {
|
|
11
|
+
"capture-gate": {
|
|
12
|
+
"step": "capture",
|
|
13
|
+
"expects": [
|
|
14
|
+
{
|
|
15
|
+
"id": "raw-text-received",
|
|
16
|
+
"kind": "surface.claim",
|
|
17
|
+
"required": true,
|
|
18
|
+
"description": "Raw text and metadata have been received and are non-empty.",
|
|
19
|
+
"claim": {
|
|
20
|
+
"type": "knowledge.ingest.capture",
|
|
21
|
+
"subject": "artifact",
|
|
22
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"classify-gate": {
|
|
28
|
+
"step": "classify",
|
|
29
|
+
"expects": [
|
|
30
|
+
{
|
|
31
|
+
"id": "classification-recorded",
|
|
32
|
+
"kind": "surface.claim",
|
|
33
|
+
"required": true,
|
|
34
|
+
"description": "The raw record has been classified and stored with a non-empty category and type='raw', satisfying the store contract's create op provenance requirements.",
|
|
35
|
+
"claim": {
|
|
36
|
+
"type": "knowledge.ingest.classification",
|
|
37
|
+
"subject": "artifact",
|
|
38
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"route-gate": {
|
|
44
|
+
"step": "route",
|
|
45
|
+
"expects": [
|
|
46
|
+
{
|
|
47
|
+
"id": "routing-decision",
|
|
48
|
+
"kind": "surface.claim",
|
|
49
|
+
"required": true,
|
|
50
|
+
"description": "A routing decision has been recorded for the classified raw record (e.g. queue for compile, discard, or hold).",
|
|
51
|
+
"claim": {
|
|
52
|
+
"type": "knowledge.ingest.routing",
|
|
53
|
+
"subject": "decision",
|
|
54
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "knowledge.store-contract",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "verify-contract", "next": "done" },
|
|
6
|
+
{ "id": "done", "next": null }
|
|
7
|
+
],
|
|
8
|
+
"gates": {
|
|
9
|
+
"verify-contract-gate": {
|
|
10
|
+
"step": "verify-contract",
|
|
11
|
+
"expects": [
|
|
12
|
+
{
|
|
13
|
+
"id": "contract-suite-passed",
|
|
14
|
+
"kind": "surface.claim",
|
|
15
|
+
"required": true,
|
|
16
|
+
"description": "The contract test suite has passed 100% against the target adapter, verifying round-trip integrity, required-evidence enforcement, and graph-index consistency.",
|
|
17
|
+
"claim": {
|
|
18
|
+
"type": "knowledge.store-contract.suite-result",
|
|
19
|
+
"subject": "artifact",
|
|
20
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "mutation-provenance-enforced",
|
|
25
|
+
"kind": "surface.claim",
|
|
26
|
+
"required": true,
|
|
27
|
+
"description": "Every mutation operation (create, update, link, propose, apply, reject) rejects requests missing required provenance fields, as verified by the contract suite.",
|
|
28
|
+
"claim": {
|
|
29
|
+
"type": "knowledge.store-contract.provenance-enforcement",
|
|
30
|
+
"subject": "artifact",
|
|
31
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "round-trip-integrity",
|
|
36
|
+
"kind": "surface.claim",
|
|
37
|
+
"required": true,
|
|
38
|
+
"description": "Records round-trip raw to stored to queried with category and links intact, as verified by the contract suite.",
|
|
39
|
+
"claim": {
|
|
40
|
+
"type": "knowledge.store-contract.round-trip",
|
|
41
|
+
"subject": "artifact",
|
|
42
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "knowledge.synthesize",
|
|
3
|
+
"version": "1.0",
|
|
4
|
+
"steps": [
|
|
5
|
+
{ "id": "detect-cluster", "next": "propose" },
|
|
6
|
+
{ "id": "propose", "next": "evidence-gate" },
|
|
7
|
+
{ "id": "evidence-gate", "next": "apply-or-reject" },
|
|
8
|
+
{ "id": "apply-or-reject", "next": "done" },
|
|
9
|
+
{ "id": "done", "next": null }
|
|
10
|
+
],
|
|
11
|
+
"gates": {
|
|
12
|
+
"detect-cluster-gate": {
|
|
13
|
+
"step": "detect-cluster",
|
|
14
|
+
"expects": [
|
|
15
|
+
{
|
|
16
|
+
"id": "similar-sources-found",
|
|
17
|
+
"kind": "surface.claim",
|
|
18
|
+
"required": true,
|
|
19
|
+
"description": "At least one compiled record similar to the target concept has been identified via the similarity detector. Similarity v1 uses category match + link-overlap heuristic. The result is a cluster of source record IDs to synthesize from.",
|
|
20
|
+
"claim": {
|
|
21
|
+
"type": "knowledge.synthesize.cluster",
|
|
22
|
+
"subject": "artifact",
|
|
23
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"propose-gate": {
|
|
29
|
+
"step": "propose",
|
|
30
|
+
"expects": [
|
|
31
|
+
{
|
|
32
|
+
"id": "proposal-recorded",
|
|
33
|
+
"kind": "surface.claim",
|
|
34
|
+
"required": true,
|
|
35
|
+
"description": "A proposal carrying source refs (proposer_id + source_ids in evidence) has been recorded via the store propose op. The concept body is NOT modified at this step — only a proposes link and mutation log entry are created.",
|
|
36
|
+
"claim": {
|
|
37
|
+
"type": "knowledge.synthesize.proposal",
|
|
38
|
+
"subject": "artifact",
|
|
39
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
"evidence-gate": {
|
|
45
|
+
"step": "evidence-gate",
|
|
46
|
+
"expects": [
|
|
47
|
+
{
|
|
48
|
+
"id": "proposal-carries-source-refs",
|
|
49
|
+
"kind": "surface.claim",
|
|
50
|
+
"required": true,
|
|
51
|
+
"description": "The proposal evidence includes source_ids referencing every compiled record that contributed to the proposed summary. Gate rejects if source_ids is empty or any referenced record does not exist.",
|
|
52
|
+
"claim": {
|
|
53
|
+
"type": "knowledge.synthesize.evidence",
|
|
54
|
+
"subject": "artifact",
|
|
55
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
},
|
|
60
|
+
"apply-gate": {
|
|
61
|
+
"step": "apply-or-reject",
|
|
62
|
+
"expects": [
|
|
63
|
+
{
|
|
64
|
+
"id": "mutation-gate-decision",
|
|
65
|
+
"kind": "surface.claim",
|
|
66
|
+
"required": true,
|
|
67
|
+
"description": "A gate decision (apply or reject) has been recorded. If applied: concept body is updated via the store apply op with rationale and all contributing source_ids in provenance. If rejected: the store reject op is called, the concept body is byte-identical to its pre-proposal state, and only a rejection log entry is appended.",
|
|
68
|
+
"claim": {
|
|
69
|
+
"type": "knowledge.synthesize.gate-decision",
|
|
70
|
+
"subject": "artifact",
|
|
71
|
+
"accepted_statuses": ["trusted", "accepted"]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schema_version": "1.0",
|
|
3
|
+
"id": "knowledge",
|
|
4
|
+
"name": "Knowledge Kit",
|
|
5
|
+
"product_name": "Knowledge Kit",
|
|
6
|
+
"description": "A Flow Kit for durable, gated knowledge storage. Provides a store contract with defined record types, mutation operations, and provenance rules \u2014 plus a default adapter backed by markdown files, YAML frontmatter, wikilinks, and a graph index.",
|
|
7
|
+
"flows": [
|
|
8
|
+
{
|
|
9
|
+
"id": "knowledge.store-contract",
|
|
10
|
+
"path": "flows/store-contract.flow.json",
|
|
11
|
+
"description": "Gate that requires contract-suite evidence before a store implementation is accepted."
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"id": "knowledge.ingest",
|
|
15
|
+
"path": "flows/ingest.flow.json",
|
|
16
|
+
"description": "Ingest raw captures: capture \u2192 classify \u2192 route. Gate on classify requires category + type='raw' classification evidence per the store contract create op."
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
"id": "knowledge.compile",
|
|
20
|
+
"path": "flows/compile.flow.json",
|
|
21
|
+
"description": "Compile classified raws into durable notes: select-raws \u2192 compile \u2192 link. Gate on compile requires provenance refs to every consumed raw record."
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "knowledge.synthesize",
|
|
25
|
+
"path": "flows/synthesize.flow.json",
|
|
26
|
+
"description": "Synthesize compiled sources into concept summaries: detect-cluster \u2192 propose \u2192 evidence-gate \u2192 apply-or-reject. Mutation-gate policy: no summary mutates without an approved proposal carrying source refs; rejection leaves store untouched."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "knowledge.consolidate",
|
|
30
|
+
"path": "flows/consolidate.flow.json",
|
|
31
|
+
"description": "Consolidate related compiled records into a living decision snapshot: related-event trigger -> consolidation proposal -> evidence gate -> apply-or-reject. Creates or updates a snapshot record; supersedes prior snapshots via supersede op (never deletes). Reuses S3 mutation-gate machinery."
|
|
32
|
+
}
|
|
33
|
+
],
|
|
34
|
+
"docs": [
|
|
35
|
+
{
|
|
36
|
+
"id": "knowledge.readme",
|
|
37
|
+
"path": "docs/README.md"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": "knowledge.store-contract-doc",
|
|
41
|
+
"path": "docs/store-contract.md"
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
"adapters": [
|
|
45
|
+
{
|
|
46
|
+
"id": "knowledge.default-store",
|
|
47
|
+
"path": "adapters/default-store/index.js",
|
|
48
|
+
"description": "Default store adapter: markdown files + YAML frontmatter + wikilinks + JSON graph index."
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
"id": "knowledge.flow-runner",
|
|
52
|
+
"path": "adapters/flow-runner/index.js",
|
|
53
|
+
"description": "Executable flow logic: capture(rawText, meta) \u2192 classified raw record; compile(rawIds[]) \u2192 compiled record with provenance links; synthesize(conceptId | topicSelector, options) \u2192 concept summary proposal with mutation gate; consolidate(snapshotId | topicSelector, options) \u2192 decision snapshot consolidation with supersede-not-delete. Emits canonical telemetry events at gate points."
|
|
54
|
+
}
|
|
55
|
+
],
|
|
56
|
+
"evals": [
|
|
57
|
+
{
|
|
58
|
+
"id": "knowledge.contract-suite",
|
|
59
|
+
"path": "evals/contract-suite/suite.test.js",
|
|
60
|
+
"description": "Parameterized contract test suite for any Knowledge Kit store adapter."
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "knowledge.ingest-compile-suite",
|
|
64
|
+
"path": "evals/ingest-compile/suite.test.js",
|
|
65
|
+
"description": "Eval cases for ingest (AC-mapped), compile provenance gate, telemetry emission, and ref resolution."
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "knowledge.synthesis-suite",
|
|
69
|
+
"path": "evals/synthesis/suite.test.js",
|
|
70
|
+
"description": "Eval cases for synthesize: AC1 (similar source \u2192 proposal not mutation), AC2 (rejection leaves concept byte-identical), AC3 (apply updates with provenance to all contributing sources), pluggable-similarity interface test, gate telemetry."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": "knowledge.consolidation-suite",
|
|
74
|
+
"path": "evals/consolidation/suite.test.js",
|
|
75
|
+
"description": "Eval cases for consolidate: AC1 (related event -> proposal, not mutation); AC2 (apply updates exactly ONE snapshot, links supersedes refs, superseded sources still queryable); AC3 fixture (decision changed across 3 events -> snapshot reflects ONLY latest decision, provenance to all 3); supersede-not-delete invariant; gate telemetry; contract suite extensions for snapshot semantics."
|
|
76
|
+
}
|
|
77
|
+
]
|
|
78
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kontourai/flow-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Flow Agents — a Kontour product that applies Flow and Veritas discipline as a portable process layer inside the agent tools you already use: Claude Code, Codex, Kiro, opencode, pi, and GitHub Actions — with framework adapters (AWS Strands preview) on the same policy-engine contract.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
package/src/cli/flow-kit.ts
CHANGED
|
@@ -4,7 +4,7 @@ import * as path from "node:path";
|
|
|
4
4
|
import { parseArgs, flagBool, flagString } from "../lib/args.js";
|
|
5
5
|
import { assertPathContained, copyDir, isoNow, readJson, walkFiles, writeJson } from "../lib/fs.js";
|
|
6
6
|
import { assertKitRepository } from "../flow-kit/validate.js";
|
|
7
|
-
import { activateCodexLocal } from "../runtime-adapters.js";
|
|
7
|
+
import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
|
|
8
8
|
|
|
9
9
|
const REGISTRY_REL = path.join("kits", "local", "installed-kits.json");
|
|
10
10
|
const REPOSITORIES_REL = path.join("kits", "local", "repositories");
|
|
@@ -112,16 +112,22 @@ function status(argv: string[]): number {
|
|
|
112
112
|
return 0;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
+
// Available adapters for the activate subcommand (Issue #32: added strands-local).
|
|
116
|
+
const AVAILABLE_ADAPTERS = ["codex-local", "strands-local"];
|
|
117
|
+
|
|
115
118
|
function activate(argv: string[]): number {
|
|
116
119
|
const args = parseArgs(argv);
|
|
117
120
|
const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
|
|
118
121
|
const sourceRoot = path.resolve(flagString(args.flags, "source-root", path.resolve(path.dirname(process.argv[1]), "..")) ?? ".");
|
|
119
122
|
const adapter = flagString(args.flags, "adapter");
|
|
120
|
-
if (adapter && adapter
|
|
121
|
-
console.log(JSON.stringify({ selected_adapter: null, available_adapters:
|
|
123
|
+
if (adapter && !AVAILABLE_ADAPTERS.includes(adapter)) {
|
|
124
|
+
console.log(JSON.stringify({ selected_adapter: null, available_adapters: AVAILABLE_ADAPTERS, supported_asset_classes: [], generated_runtime_files: [], skipped_assets: [], warnings: [], errors: [`unknown runtime adapter '${adapter}'; available adapters: ${AVAILABLE_ADAPTERS.join(", ")}`] }, null, 2));
|
|
122
125
|
return 2;
|
|
123
126
|
}
|
|
124
|
-
|
|
127
|
+
// Default to codex-local for backward compatibility; strands-local is opt-in via --adapter.
|
|
128
|
+
const result = adapter === "strands-local"
|
|
129
|
+
? activateStrandsLocal(sourceRoot, dest)
|
|
130
|
+
: activateCodexLocal(sourceRoot, dest);
|
|
125
131
|
console.log(JSON.stringify(result, null, 2));
|
|
126
132
|
return Array.isArray(result.errors) && result.errors.length ? 1 : 0;
|
|
127
133
|
}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
2
|
import { parseArgs, flagString } from "../lib/args.js";
|
|
3
|
-
import { activateCodexLocal } from "../runtime-adapters.js";
|
|
3
|
+
import { activateCodexLocal, activateStrandsLocal } from "../runtime-adapters.js";
|
|
4
|
+
|
|
5
|
+
const AVAILABLE_ADAPTERS = ["codex-local", "strands-local"];
|
|
4
6
|
|
|
5
7
|
export function main(argv = process.argv.slice(2)): number {
|
|
6
8
|
const [command, ...rest] = argv;
|
|
7
9
|
if (command !== "activate") {
|
|
8
|
-
console.error("usage: runtime-adapter activate [--dest DIR] [--source-root DIR] [--adapter codex-local]");
|
|
10
|
+
console.error("usage: runtime-adapter activate [--dest DIR] [--source-root DIR] [--adapter codex-local|strands-local]");
|
|
9
11
|
return 2;
|
|
10
12
|
}
|
|
11
13
|
const args = parseArgs(rest);
|
|
12
14
|
const dest = path.resolve(flagString(args.flags, "dest", ".") ?? ".");
|
|
13
15
|
const sourceRoot = path.resolve(flagString(args.flags, "source-root", path.resolve(path.dirname(process.argv[1]), "..")) ?? ".");
|
|
14
16
|
const adapter = flagString(args.flags, "adapter");
|
|
15
|
-
if (adapter && adapter
|
|
16
|
-
console.log(JSON.stringify({ selected_adapter: null, available_adapters:
|
|
17
|
+
if (adapter && !AVAILABLE_ADAPTERS.includes(adapter)) {
|
|
18
|
+
console.log(JSON.stringify({ selected_adapter: null, available_adapters: AVAILABLE_ADAPTERS, supported_asset_classes: [], generated_runtime_files: [], skipped_assets: [], warnings: [], errors: [`unknown runtime adapter '${adapter}'; available adapters: ${AVAILABLE_ADAPTERS.join(", ")}`] }, null, 2));
|
|
17
19
|
return 2;
|
|
18
20
|
}
|
|
19
|
-
|
|
21
|
+
// Default to codex-local for backward compatibility; strands-local is opt-in via --adapter.
|
|
22
|
+
const result = adapter === "strands-local"
|
|
23
|
+
? activateStrandsLocal(sourceRoot, dest)
|
|
24
|
+
: activateCodexLocal(sourceRoot, dest);
|
|
20
25
|
console.log(JSON.stringify(result, null, 2));
|
|
21
26
|
return Array.isArray(result.errors) && result.errors.length ? 1 : 0;
|
|
22
27
|
}
|
|
@@ -82,7 +82,10 @@ function channelConfigValue(config: Config, channel: string, key: string, fallba
|
|
|
82
82
|
|
|
83
83
|
function telemetryDataDir(dest: string): string {
|
|
84
84
|
const configured = process.env.TELEMETRY_DATA_DIR;
|
|
85
|
-
|
|
85
|
+
// Must mirror scripts/telemetry/lib/config.sh: the sink lives INSIDE the
|
|
86
|
+
// workspace at <dest>/.telemetry. The previous "../.telemetry" duplicated
|
|
87
|
+
// the parent-escape bug fixed in config.sh on 2026-06-11.
|
|
88
|
+
return configured ? path.resolve(dest, configured) : path.resolve(dest, ".telemetry");
|
|
86
89
|
}
|
|
87
90
|
|
|
88
91
|
function deriveConsoleEndpoint(consoleUrl: string, explicitEndpoint: string): string {
|
package/src/runtime-adapters.ts
CHANGED
|
@@ -152,3 +152,38 @@ export function activateCodexLocal(sourceRoot: string, dest: string): Record<str
|
|
|
152
152
|
generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "codex-local.activation", source_path: manifestPath.split(path.sep).join("/") });
|
|
153
153
|
return { selected_adapter: "codex-local", supported_asset_classes: ["flows"], generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
154
154
|
}
|
|
155
|
+
|
|
156
|
+
// Decision Q3 (Issue #32): Option (a) — new adapter id "strands-local" rather than
|
|
157
|
+
// loading kit flows inside FlowAgentsHooks. Rationale: activation is a workspace-prep
|
|
158
|
+
// concern (reads catalog + installed kits, writes runtime files, produces diagnostics).
|
|
159
|
+
// Keeping it in the CLI adapter layer maximises reuse of readKitInventory and safeSegment,
|
|
160
|
+
// mirrors the codex-local pattern exactly, and keeps framework adapters free of catalog-
|
|
161
|
+
// layout knowledge. The Strands steering layer then reads the written runtime files.
|
|
162
|
+
export function activateStrandsLocal(sourceRoot: string, dest: string): Record<string, unknown> {
|
|
163
|
+
const inventory = readKitInventory(sourceRoot, dest);
|
|
164
|
+
// Runtime flows land at .flow-agents/runtime/strands/flows/<kit-id>/<asset-id>.flow.json
|
|
165
|
+
// so the Strands steering context can glob for *.flow.json under this path.
|
|
166
|
+
const runtimeDir = path.join(dest, ".flow-agents", "runtime", "strands");
|
|
167
|
+
const generated: Record<string, string>[] = [];
|
|
168
|
+
const skipped: Record<string, string | null>[] = [];
|
|
169
|
+
for (const asset of inventory.assets) {
|
|
170
|
+
if (asset.asset_class !== "flows") {
|
|
171
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: asset.asset_id, reason: "asset class is diagnostic-only for strands-local" });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
if (!asset.asset_id) {
|
|
175
|
+
skipped.push({ asset_class: asset.asset_class, path: asset.relative_path, kit_id: asset.kit_id, asset_id: null, reason: "flow asset is missing an id" });
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
const output = path.join(runtimeDir, "flows", safeSegment(asset.kit_id), `${safeSegment(asset.asset_id)}.flow.json`);
|
|
179
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
180
|
+
fs.copyFileSync(asset.source_path, output);
|
|
181
|
+
generated.push({ asset_class: asset.asset_class, path: relPath(dest, output), kit_id: asset.kit_id, asset_id: asset.asset_id, source_path: asset.source_path.split(path.sep).join("/") });
|
|
182
|
+
}
|
|
183
|
+
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
184
|
+
const manifest = { schema_version: "1.0", adapter: "strands-local", supported_asset_classes: ["flows"], generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
185
|
+
const manifestPath = path.join(runtimeDir, "activation.json");
|
|
186
|
+
writeJson(manifestPath, manifest);
|
|
187
|
+
generated.push({ asset_class: "activation-manifest", path: relPath(dest, manifestPath), kit_id: "runtime", asset_id: "strands-local.activation", source_path: manifestPath.split(path.sep).join("/") });
|
|
188
|
+
return { selected_adapter: "strands-local", supported_asset_classes: ["flows"], generated_runtime_files: generated, skipped_assets: skipped, warnings: inventory.warnings, errors: inventory.errors };
|
|
189
|
+
}
|
|
@@ -355,6 +355,15 @@ function exportOpencodePlugin(): string {
|
|
|
355
355
|
// (for session-start steering context) and tool.execute.before (for
|
|
356
356
|
// policy). This is the closest reasonable approximation — documented here
|
|
357
357
|
// as an honest gap matching the codex live-hook-influence caveat pattern.
|
|
358
|
+
//
|
|
359
|
+
// KNOWN GAP (verified 2026-06-11, opencode v1.16.2): session.created is NOT
|
|
360
|
+
// delivered to plugin event handlers in opencode (non-interactive) mode.
|
|
361
|
+
// The session IS created server-side but the event fires before the plugin
|
|
362
|
+
// hook dispatch loop is active. As a result, agentSpawn telemetry (session.start)
|
|
363
|
+
// is never emitted in run-mode sessions — only tool.invoke/tool.result appear.
|
|
364
|
+
// This is an opencode runtime limitation, not a bug in this plugin.
|
|
365
|
+
// Session.start telemetry carries L1 conformance but is unavailable in run mode.
|
|
366
|
+
// See docs/spec/runtime-hook-surface.md opencode mapping row for the full gap note.
|
|
358
367
|
return `/**
|
|
359
368
|
* Flow Agents opencode plugin.
|
|
360
369
|
*
|
|
@@ -369,6 +378,13 @@ function exportOpencodePlugin(): string {
|
|
|
369
378
|
* cannot intercept mid-session user messages before they are processed.
|
|
370
379
|
* This is an accepted gap documented here analogously to the codex
|
|
371
380
|
* live-hook-influence caveat.
|
|
381
|
+
*
|
|
382
|
+
* KNOWN GAP: session.created is NOT delivered to plugin handlers in opencode
|
|
383
|
+
* run (non-interactive) mode (verified v1.16.2, 2026-06-11). agentSpawn
|
|
384
|
+
* telemetry (session.start) is therefore absent from run-mode sessions.
|
|
385
|
+
* tool.invoke and tool.result events (L1) are still recorded normally.
|
|
386
|
+
* This is an opencode runtime limitation; no workaround is available without
|
|
387
|
+
* a different hook surface. See docs/spec/runtime-hook-surface.md for details.
|
|
372
388
|
*/
|
|
373
389
|
|
|
374
390
|
import { spawnSync } from 'node:child_process';
|
|
@@ -547,7 +563,8 @@ export default function (pi: ExtensionAPI) {
|
|
|
547
563
|
});
|
|
548
564
|
|
|
549
565
|
pi.on("before_agent_start", async (event, _ctx) => {
|
|
550
|
-
|
|
566
|
+
// Telemetry for agentSpawn is emitted by session_start above; do not repeat it here
|
|
567
|
+
// to avoid duplicate session.start events in the telemetry log.
|
|
551
568
|
// Inject workflow steering context at agent start
|
|
552
569
|
const result = runAdapter("pi-hook-adapter.js", "before_agent_start", "workflow-steering", "workflow-steering.js");
|
|
553
570
|
if (result.context) {
|