dna-sdk 0.9.0 → 0.11.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/dist/bootstrap.js +4 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.js +10 -1
- package/dist/emit/agentFramework.d.ts +2 -0
- package/dist/emit/agentFramework.js +6 -0
- package/dist/emit/agno.d.ts +25 -0
- package/dist/emit/agno.js +65 -0
- package/dist/emit/bedrock.d.ts +3 -0
- package/dist/emit/bedrock.js +9 -0
- package/dist/emit/deepagents.d.ts +25 -0
- package/dist/emit/deepagents.js +71 -0
- package/dist/emit/index.d.ts +33 -2
- package/dist/emit/index.js +19 -1
- package/dist/emit/langgraph.d.ts +26 -0
- package/dist/emit/langgraph.js +70 -0
- package/dist/emit/openaiAgents.d.ts +26 -0
- package/dist/emit/openaiAgents.js +91 -0
- package/dist/emit/scaffold.d.ts +65 -0
- package/dist/emit/scaffold.js +179 -0
- package/dist/emit/scaffolds/agno/prompt-only.py.tmpl +15 -0
- package/dist/emit/scaffolds/agno/with-tools.py.tmpl +23 -0
- package/dist/emit/scaffolds/deepagents/prompt-only.py.tmpl +16 -0
- package/dist/emit/scaffolds/deepagents/with-tools.py.tmpl +22 -0
- package/dist/emit/scaffolds/langgraph/prompt-only.py.tmpl +16 -0
- package/dist/emit/scaffolds/langgraph/with-tools.py.tmpl +25 -0
- package/dist/emit/scaffolds/openai-agents/prompt-only.py.tmpl +15 -0
- package/dist/emit/scaffolds/openai-agents/with-tools.py.tmpl +24 -0
- package/dist/emit/vertex.d.ts +3 -0
- package/dist/emit/vertex.js +7 -0
- package/dist/extensions/cloud/kinds/tenant-plan.kind.yaml +105 -0
- package/dist/extensions/cloud/kinds/tier.kind.yaml +135 -0
- package/dist/extensions/cloud.d.ts +31 -0
- package/dist/extensions/cloud.js +39 -0
- package/dist/extensions/intel/kinds/insight.kind.yaml +148 -0
- package/dist/extensions/intel/kinds/intel-source.kind.yaml +117 -0
- package/dist/extensions/intel.d.ts +30 -0
- package/dist/extensions/intel.js +38 -0
- package/dist/extensions/sdlc/kinds/lesson-learned.kind.yaml +0 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/kernel/index.d.ts +44 -0
- package/dist/kernel/index.js +85 -0
- package/package.json +1 -1
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type EmitContext, type EmitResult, type EmitterPort } from "./index.js";
|
|
2
|
+
/** Render `value` as a Python string literal that round-trips exactly. JSON
|
|
3
|
+
* string syntax is a valid Python literal (`\n`, `\"`, `\\`, `\uXXXX`), single
|
|
4
|
+
* line — so the emitted `INSTRUCTIONS` constant is byte-equal-recoverable. */
|
|
5
|
+
export declare function pyStrLiteral(value: string): string;
|
|
6
|
+
/** `kb-search` → `kb_search` — a valid Python identifier for a tool stub. */
|
|
7
|
+
export declare function pyIdentifier(name: string): string;
|
|
8
|
+
/** The default case classifier — read the DNA signals the ctx already carries. */
|
|
9
|
+
export declare function classifyCase(ctx: EmitContext): string;
|
|
10
|
+
/** Resolve a `{framework × case}` template to its Mustache source — the abstract
|
|
11
|
+
* seam between an emitter and *where a template lives*. The MVP reads
|
|
12
|
+
* package-data ({@link PackageDataScaffoldResolver}); a future kernel-backed
|
|
13
|
+
* resolver returns a per-scope/per-tenant Scaffold Kind body, swappable with no
|
|
14
|
+
* emitter change. Returns null when the source has no template for the pair. */
|
|
15
|
+
export interface ScaffoldResolver {
|
|
16
|
+
resolve(framework: string, kase: string): string | null;
|
|
17
|
+
}
|
|
18
|
+
/** The MVP resolver: read `emit/scaffolds/<framework>/<case>.py.tmpl`. */
|
|
19
|
+
export declare class PackageDataScaffoldResolver implements ScaffoldResolver {
|
|
20
|
+
resolve(framework: string, kase: string): string | null;
|
|
21
|
+
}
|
|
22
|
+
/** Swap the active template resolver — the seam the Scaffold-as-Kind promotion
|
|
23
|
+
* (`s-scaffold-as-kind`) plugs into. */
|
|
24
|
+
export declare function setScaffoldResolver(resolver: ScaffoldResolver): ScaffoldResolver;
|
|
25
|
+
/** Resolve a `{framework × case}` template through the active resolver. */
|
|
26
|
+
export declare function resolveScaffold(framework: string, kase: string): string | null;
|
|
27
|
+
/** The outcome of {@link selectScaffold}: which case template to fill. */
|
|
28
|
+
export interface ScaffoldChoice {
|
|
29
|
+
/** The case actually selected (a template exists for it). */
|
|
30
|
+
case: string;
|
|
31
|
+
/** The template source (Mustache). */
|
|
32
|
+
template: string;
|
|
33
|
+
/** The case the classifier requested — differs from `case` on a fallback. */
|
|
34
|
+
requested: string;
|
|
35
|
+
}
|
|
36
|
+
/** Pick the `{framework × case}` template for `ctx`: classify, then resolve to a
|
|
37
|
+
* real template, falling back down the generality chain when needed. */
|
|
38
|
+
export declare function selectScaffold(framework: string, ctx: EmitContext, classify?: (ctx: EmitContext) => string, resolver?: ScaffoldResolver | null): ScaffoldChoice;
|
|
39
|
+
/** Base for a CODE-FIRST {@link EmitterPort}. A subclass is THIN — it declares the
|
|
40
|
+
* framework + target ids and supplies the framework-specific template variables
|
|
41
|
+
* ({@link renderContext}), losses, and field mapping; case selection, template
|
|
42
|
+
* fill, and the byte-equal invariant hook are inherited. */
|
|
43
|
+
export declare abstract class ScaffoldEmitter implements EmitterPort {
|
|
44
|
+
abstract readonly framework: string;
|
|
45
|
+
abstract readonly target: string;
|
|
46
|
+
readonly fileExtension: string;
|
|
47
|
+
/** Optional template-resolution override (defaults to the active resolver —
|
|
48
|
+
* package-data today, a Scaffold Kind resolver tomorrow). */
|
|
49
|
+
readonly resolver: ScaffoldResolver | null;
|
|
50
|
+
/** Framework-specific template variables (merged over the common ones). */
|
|
51
|
+
renderContext(_ctx: EmitContext, _case: string): Record<string, unknown>;
|
|
52
|
+
/** Framework-specific de-para losses (in addition to the common ones). */
|
|
53
|
+
losses(_ctx: EmitContext, _choice: ScaffoldChoice): string[];
|
|
54
|
+
/** Field-level de-para (`dnaField -> targetField`) for reporting. */
|
|
55
|
+
mapping(): Record<string, string>;
|
|
56
|
+
/** The case classifier — override to add a framework-specific case. */
|
|
57
|
+
classify(ctx: EmitContext): string;
|
|
58
|
+
private commonContext;
|
|
59
|
+
private commonLosses;
|
|
60
|
+
emit(ctx: EmitContext): EmitResult;
|
|
61
|
+
/** Byte-equal invariant hook: read the top-level `INSTRUCTIONS = <literal>`
|
|
62
|
+
* constant every scaffold template emits (a JSON/Python string literal on one
|
|
63
|
+
* line), uniform across all code-first targets regardless of constructor shape. */
|
|
64
|
+
extractInstructions(artifact: string): string | null;
|
|
65
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `emit/scaffold` — the CODE-FIRST flavor of the {@link EmitterPort} (TS twin of
|
|
3
|
+
* python `dna.emit.scaffold`).
|
|
4
|
+
*
|
|
5
|
+
* Some runtimes have NO declarative agent format — you construct an agent object
|
|
6
|
+
* in code (OpenAI Agents SDK, LangGraph, Agno, DeepAgents all expose a simple
|
|
7
|
+
* constructor). For those the emitter must produce SOURCE CODE, and it does so by
|
|
8
|
+
* **filling a curated template**, never by generating code ad-hoc — the template
|
|
9
|
+
* captures the framework's best-practice idiom; the emitter only routes and fills.
|
|
10
|
+
*
|
|
11
|
+
* The mechanism is a template library indexed by `{framework × case}` plus a tiny
|
|
12
|
+
* case classifier:
|
|
13
|
+
*
|
|
14
|
+
* scaffolds/<framework>/<case>.py.tmpl // curated best-practice idiom per case
|
|
15
|
+
* selectScaffold(framework, ctx) // inspect ctx's DNA signals → pick a case
|
|
16
|
+
*
|
|
17
|
+
* There is deliberately NO single "one template per framework": a prompt-only, a
|
|
18
|
+
* tool-calling (ReAct), and a structured-output agent are DIFFERENT structures in
|
|
19
|
+
* the SAME framework. The classifier reads the neutral {@link EmitContext} signals
|
|
20
|
+
* (no tools → `prompt-only`; tools → `with-tools`; `outputSchema` →
|
|
21
|
+
* `structured-output`) and falls back down a generality chain when a framework
|
|
22
|
+
* does not ship a case, recording the fallback as a loss.
|
|
23
|
+
*
|
|
24
|
+
* Future direction — Scaffold as a Kind: templates are read through an abstract
|
|
25
|
+
* seam, {@link resolveScaffold} (a {@link ScaffoldResolver}), NOT a hardcoded file
|
|
26
|
+
* path. The MVP resolver reads package-data, but the seam lets a second source
|
|
27
|
+
* plug in with no change to any emitter — a first-class **Scaffold Kind** resolved
|
|
28
|
+
* by the kernel (scope-aware, tenant-overridable). Tracked as `s-scaffold-as-kind`.
|
|
29
|
+
*/
|
|
30
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import Mustache from "mustache";
|
|
34
|
+
import { EmitError } from "./index.js";
|
|
35
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
36
|
+
/** The generality fallback chain (specific → general) per requested case. */
|
|
37
|
+
const FALLBACK = {
|
|
38
|
+
"structured-output": ["structured-output", "with-tools", "prompt-only"],
|
|
39
|
+
"with-tools": ["with-tools", "prompt-only"],
|
|
40
|
+
"prompt-only": ["prompt-only"],
|
|
41
|
+
};
|
|
42
|
+
/** Render `value` as a Python string literal that round-trips exactly. JSON
|
|
43
|
+
* string syntax is a valid Python literal (`\n`, `\"`, `\\`, `\uXXXX`), single
|
|
44
|
+
* line — so the emitted `INSTRUCTIONS` constant is byte-equal-recoverable. */
|
|
45
|
+
export function pyStrLiteral(value) {
|
|
46
|
+
return JSON.stringify(value);
|
|
47
|
+
}
|
|
48
|
+
/** `kb-search` → `kb_search` — a valid Python identifier for a tool stub. */
|
|
49
|
+
export function pyIdentifier(name) {
|
|
50
|
+
let ident = String(name)
|
|
51
|
+
.trim()
|
|
52
|
+
.replace(/[^0-9a-zA-Z_]/g, "_")
|
|
53
|
+
.replace(/^_+|_+$/g, "");
|
|
54
|
+
if (!ident)
|
|
55
|
+
ident = "tool";
|
|
56
|
+
if (/^[0-9]/.test(ident))
|
|
57
|
+
ident = `_${ident}`;
|
|
58
|
+
return ident;
|
|
59
|
+
}
|
|
60
|
+
/** The default case classifier — read the DNA signals the ctx already carries. */
|
|
61
|
+
export function classifyCase(ctx) {
|
|
62
|
+
if (ctx.outputSchema)
|
|
63
|
+
return "structured-output";
|
|
64
|
+
if (ctx.tools.length > 0)
|
|
65
|
+
return "with-tools";
|
|
66
|
+
return "prompt-only";
|
|
67
|
+
}
|
|
68
|
+
/** The MVP resolver: read `emit/scaffolds/<framework>/<case>.py.tmpl`. */
|
|
69
|
+
export class PackageDataScaffoldResolver {
|
|
70
|
+
resolve(framework, kase) {
|
|
71
|
+
const path = join(HERE, "scaffolds", framework, `${kase}.py.tmpl`);
|
|
72
|
+
return existsSync(path) ? readFileSync(path, "utf-8") : null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let activeResolver = new PackageDataScaffoldResolver();
|
|
76
|
+
/** Swap the active template resolver — the seam the Scaffold-as-Kind promotion
|
|
77
|
+
* (`s-scaffold-as-kind`) plugs into. */
|
|
78
|
+
export function setScaffoldResolver(resolver) {
|
|
79
|
+
activeResolver = resolver;
|
|
80
|
+
return resolver;
|
|
81
|
+
}
|
|
82
|
+
/** Resolve a `{framework × case}` template through the active resolver. */
|
|
83
|
+
export function resolveScaffold(framework, kase) {
|
|
84
|
+
return activeResolver.resolve(framework, kase);
|
|
85
|
+
}
|
|
86
|
+
/** Pick the `{framework × case}` template for `ctx`: classify, then resolve to a
|
|
87
|
+
* real template, falling back down the generality chain when needed. */
|
|
88
|
+
export function selectScaffold(framework, ctx, classify = classifyCase, resolver = null) {
|
|
89
|
+
const resolve = resolver
|
|
90
|
+
? (f, k) => resolver.resolve(f, k)
|
|
91
|
+
: resolveScaffold;
|
|
92
|
+
const requested = classify(ctx);
|
|
93
|
+
for (const kase of FALLBACK[requested] ?? [requested]) {
|
|
94
|
+
const template = resolve(framework, kase);
|
|
95
|
+
if (template !== null)
|
|
96
|
+
return { case: kase, template, requested };
|
|
97
|
+
}
|
|
98
|
+
throw new EmitError(`no scaffold template for framework '${framework}' ` +
|
|
99
|
+
`(looked for case '${requested}' and its fallbacks)`);
|
|
100
|
+
}
|
|
101
|
+
/** Base for a CODE-FIRST {@link EmitterPort}. A subclass is THIN — it declares the
|
|
102
|
+
* framework + target ids and supplies the framework-specific template variables
|
|
103
|
+
* ({@link renderContext}), losses, and field mapping; case selection, template
|
|
104
|
+
* fill, and the byte-equal invariant hook are inherited. */
|
|
105
|
+
export class ScaffoldEmitter {
|
|
106
|
+
fileExtension = "py";
|
|
107
|
+
/** Optional template-resolution override (defaults to the active resolver —
|
|
108
|
+
* package-data today, a Scaffold Kind resolver tomorrow). */
|
|
109
|
+
resolver = null;
|
|
110
|
+
// ── the hooks a subclass overrides ────────────────────────────────────────
|
|
111
|
+
/** Framework-specific template variables (merged over the common ones). */
|
|
112
|
+
renderContext(_ctx, _case) {
|
|
113
|
+
return {};
|
|
114
|
+
}
|
|
115
|
+
/** Framework-specific de-para losses (in addition to the common ones). */
|
|
116
|
+
losses(_ctx, _choice) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
/** Field-level de-para (`dnaField -> targetField`) for reporting. */
|
|
120
|
+
mapping() {
|
|
121
|
+
return {};
|
|
122
|
+
}
|
|
123
|
+
/** The case classifier — override to add a framework-specific case. */
|
|
124
|
+
classify(ctx) {
|
|
125
|
+
return classifyCase(ctx);
|
|
126
|
+
}
|
|
127
|
+
// ── the inherited machinery ───────────────────────────────────────────────
|
|
128
|
+
commonContext(ctx) {
|
|
129
|
+
return {
|
|
130
|
+
name: ctx.name,
|
|
131
|
+
name_literal: pyStrLiteral(ctx.name),
|
|
132
|
+
description: ctx.description,
|
|
133
|
+
has_description: Boolean(ctx.description),
|
|
134
|
+
// INSTRUCTIONS constant — byte-equal, recoverable via extractInstructions.
|
|
135
|
+
instructions_literal: pyStrLiteral(ctx.instructions),
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
commonLosses(ctx, choice) {
|
|
139
|
+
const losses = [
|
|
140
|
+
"composition structure — Soul reuse + wired Guardrails flatten to one " +
|
|
141
|
+
"`INSTRUCTIONS` string (a code-first agent has no `soul:`/`guardrails:` slot)",
|
|
142
|
+
"tenant overlay — a per-tenant persona without a fork has no code-first field",
|
|
143
|
+
"eval-as-contract — prompt invariants (EvalCases) have no code-first slot",
|
|
144
|
+
];
|
|
145
|
+
if (choice.case !== choice.requested) {
|
|
146
|
+
losses.push(`scaffold case — the '${choice.requested}' idiom is not shipped for ` +
|
|
147
|
+
`'${this.framework}'; fell back to the '${choice.case}' template ` +
|
|
148
|
+
"(structure closest to but not identical to the requested case)");
|
|
149
|
+
}
|
|
150
|
+
return losses;
|
|
151
|
+
}
|
|
152
|
+
emit(ctx) {
|
|
153
|
+
const choice = selectScaffold(this.framework, ctx, (c) => this.classify(c), this.resolver);
|
|
154
|
+
const variables = { ...this.commonContext(ctx), ...this.renderContext(ctx, choice.case) };
|
|
155
|
+
const artifact = Mustache.render(choice.template, variables);
|
|
156
|
+
const losses = [...this.commonLosses(ctx, choice), ...this.losses(ctx, choice)];
|
|
157
|
+
return {
|
|
158
|
+
artifact,
|
|
159
|
+
target: this.target,
|
|
160
|
+
filename: `${ctx.name}.${this.fileExtension}`,
|
|
161
|
+
losses,
|
|
162
|
+
mapping: this.mapping(),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Byte-equal invariant hook: read the top-level `INSTRUCTIONS = <literal>`
|
|
166
|
+
* constant every scaffold template emits (a JSON/Python string literal on one
|
|
167
|
+
* line), uniform across all code-first targets regardless of constructor shape. */
|
|
168
|
+
extractInstructions(artifact) {
|
|
169
|
+
const match = artifact.match(/^INSTRUCTIONS = (.+)$/m);
|
|
170
|
+
if (!match)
|
|
171
|
+
return null;
|
|
172
|
+
try {
|
|
173
|
+
return JSON.parse(match[1]);
|
|
174
|
+
}
|
|
175
|
+
catch {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""DNA-emitted Agno agent: {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target agno`. INSTRUCTIONS is the DNA-composed prompt
|
|
4
|
+
(Soul + guardrails + instruction), carried byte-equal from the source of truth —
|
|
5
|
+
edit the agent in DNA and re-emit, do not hand-edit the prompt here.
|
|
6
|
+
"""
|
|
7
|
+
from agno.agent import Agent
|
|
8
|
+
|
|
9
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
10
|
+
|
|
11
|
+
agent = Agent(
|
|
12
|
+
name={{{name_literal}}},
|
|
13
|
+
{{#has_model}} model={{{model_literal}}},
|
|
14
|
+
{{/has_model}} instructions=INSTRUCTIONS,
|
|
15
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""DNA-emitted Agno agent (tool-calling): {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target agno`. INSTRUCTIONS is the DNA-composed prompt
|
|
4
|
+
(Soul + guardrails + instruction), carried byte-equal. Each tool below is a STUB
|
|
5
|
+
scaffolded from a DNA Tool's name + description — wire its real implementation and
|
|
6
|
+
typed signature (Agno derives the tool schema from the function + docstring).
|
|
7
|
+
"""
|
|
8
|
+
from agno.agent import Agent
|
|
9
|
+
|
|
10
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
11
|
+
|
|
12
|
+
{{#tools}}
|
|
13
|
+
def {{func_name}}() -> str:
|
|
14
|
+
{{{docstring_literal}}}
|
|
15
|
+
raise NotImplementedError("DNA scaffold: implement the {{name}} tool")
|
|
16
|
+
|
|
17
|
+
{{/tools}}
|
|
18
|
+
agent = Agent(
|
|
19
|
+
name={{{name_literal}}},
|
|
20
|
+
{{#has_model}} model={{{model_literal}}},
|
|
21
|
+
{{/has_model}} instructions=INSTRUCTIONS,
|
|
22
|
+
tools=[{{tool_list}}],
|
|
23
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""DNA-emitted DeepAgents agent: {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target deepagents`. INSTRUCTIONS is the DNA-composed
|
|
4
|
+
prompt (Soul + guardrails + instruction), carried byte-equal from the source of
|
|
5
|
+
truth — edit the agent in DNA and re-emit, do not hand-edit the prompt here.
|
|
6
|
+
`system_prompt` sits in FRONT of the deep-agent's built-in harness prompt.
|
|
7
|
+
"""
|
|
8
|
+
from deepagents import create_deep_agent
|
|
9
|
+
|
|
10
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
11
|
+
|
|
12
|
+
agent = create_deep_agent(
|
|
13
|
+
{{#has_model}} model={{{model_literal}}},
|
|
14
|
+
{{/has_model}} tools=[],
|
|
15
|
+
system_prompt=INSTRUCTIONS,
|
|
16
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""DNA-emitted DeepAgents agent (tool-calling): {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target deepagents`. INSTRUCTIONS is the DNA-composed
|
|
4
|
+
prompt (Soul + guardrails + instruction), carried byte-equal. Each tool below is a
|
|
5
|
+
STUB scaffolded from a DNA Tool's name + description — wire its real implementation
|
|
6
|
+
and typed signature. `system_prompt` sits in FRONT of the deep-agent harness prompt.
|
|
7
|
+
"""
|
|
8
|
+
from deepagents import create_deep_agent
|
|
9
|
+
|
|
10
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
11
|
+
|
|
12
|
+
{{#tools}}
|
|
13
|
+
def {{func_name}}() -> str:
|
|
14
|
+
{{{docstring_literal}}}
|
|
15
|
+
raise NotImplementedError("DNA scaffold: implement the {{name}} tool")
|
|
16
|
+
|
|
17
|
+
{{/tools}}
|
|
18
|
+
agent = create_deep_agent(
|
|
19
|
+
{{#has_model}} model={{{model_literal}}},
|
|
20
|
+
{{/has_model}} tools=[{{tool_list}}],
|
|
21
|
+
system_prompt=INSTRUCTIONS,
|
|
22
|
+
)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""DNA-emitted LangGraph ReAct agent: {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target langgraph`. INSTRUCTIONS is the DNA-composed
|
|
4
|
+
prompt (Soul + guardrails + instruction), carried byte-equal from the source of
|
|
5
|
+
truth — edit the agent in DNA and re-emit, do not hand-edit the prompt here.
|
|
6
|
+
"""
|
|
7
|
+
from langgraph.prebuilt import create_react_agent
|
|
8
|
+
|
|
9
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
10
|
+
|
|
11
|
+
agent = create_react_agent(
|
|
12
|
+
{{#has_model}} model={{{model_literal}}},
|
|
13
|
+
{{/has_model}} tools=[],
|
|
14
|
+
prompt=INSTRUCTIONS,
|
|
15
|
+
{{#has_name}} name={{{name_literal}}},
|
|
16
|
+
{{/has_name}})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""DNA-emitted LangGraph ReAct agent (tool-calling): {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target langgraph`. INSTRUCTIONS is the DNA-composed
|
|
4
|
+
prompt (Soul + guardrails + instruction), carried byte-equal. Each @tool below is
|
|
5
|
+
a STUB scaffolded from a DNA Tool's name + description — wire its real
|
|
6
|
+
implementation and typed signature (LangChain derives the tool schema from them).
|
|
7
|
+
"""
|
|
8
|
+
from langchain_core.tools import tool
|
|
9
|
+
from langgraph.prebuilt import create_react_agent
|
|
10
|
+
|
|
11
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
12
|
+
|
|
13
|
+
{{#tools}}
|
|
14
|
+
@tool
|
|
15
|
+
def {{func_name}}() -> str:
|
|
16
|
+
{{{docstring_literal}}}
|
|
17
|
+
raise NotImplementedError("DNA scaffold: implement the {{name}} tool")
|
|
18
|
+
|
|
19
|
+
{{/tools}}
|
|
20
|
+
agent = create_react_agent(
|
|
21
|
+
{{#has_model}} model={{{model_literal}}},
|
|
22
|
+
{{/has_model}} tools=[{{tool_list}}],
|
|
23
|
+
prompt=INSTRUCTIONS,
|
|
24
|
+
{{#has_name}} name={{{name_literal}}},
|
|
25
|
+
{{/has_name}})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"""DNA-emitted OpenAI Agents SDK agent: {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target openai-agents`. INSTRUCTIONS is the DNA-composed
|
|
4
|
+
prompt (Soul + guardrails + instruction), carried byte-equal from the source of
|
|
5
|
+
truth — edit the agent in DNA and re-emit, do not hand-edit the prompt here.
|
|
6
|
+
"""
|
|
7
|
+
from agents import Agent
|
|
8
|
+
|
|
9
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
10
|
+
|
|
11
|
+
agent = Agent(
|
|
12
|
+
name={{{name_literal}}},
|
|
13
|
+
instructions=INSTRUCTIONS,
|
|
14
|
+
{{#has_model}} model={{{model_literal}}},
|
|
15
|
+
{{/has_model}})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""DNA-emitted OpenAI Agents SDK agent (tool-calling / ReAct): {{name}}.
|
|
2
|
+
|
|
3
|
+
Generated by `dna emit --target openai-agents`. INSTRUCTIONS is the DNA-composed
|
|
4
|
+
prompt (Soul + guardrails + instruction), carried byte-equal. Each @function_tool
|
|
5
|
+
below is a STUB scaffolded from a DNA Tool's name + description — wire its real
|
|
6
|
+
implementation and typed signature (the SDK derives the tool schema from them).
|
|
7
|
+
"""
|
|
8
|
+
from agents import Agent, function_tool
|
|
9
|
+
|
|
10
|
+
INSTRUCTIONS = {{{instructions_literal}}}
|
|
11
|
+
|
|
12
|
+
{{#tools}}
|
|
13
|
+
@function_tool
|
|
14
|
+
def {{func_name}}() -> str:
|
|
15
|
+
{{{docstring_literal}}}
|
|
16
|
+
raise NotImplementedError("DNA scaffold: implement the {{name}} tool")
|
|
17
|
+
|
|
18
|
+
{{/tools}}
|
|
19
|
+
agent = Agent(
|
|
20
|
+
name={{{name_literal}}},
|
|
21
|
+
instructions=INSTRUCTIONS,
|
|
22
|
+
{{#has_model}} model={{{model_literal}}},
|
|
23
|
+
{{/has_model}} tools=[{{tool_list}}],
|
|
24
|
+
)
|
package/dist/emit/vertex.d.ts
CHANGED
|
@@ -16,4 +16,7 @@ export declare class VertexEmitter implements EmitterPort {
|
|
|
16
16
|
* time, not part of this object. */
|
|
17
17
|
toAgentConfig(ctx: EmitContext): Record<string, unknown>;
|
|
18
18
|
emit(ctx: EmitContext): EmitResult;
|
|
19
|
+
/** Byte-equal invariant hook: read `instruction` back from the emitted ADK
|
|
20
|
+
* Agent Config YAML (the `# yaml-language-server` header is a YAML comment). */
|
|
21
|
+
extractInstructions(artifact: string): string | null;
|
|
19
22
|
}
|
package/dist/emit/vertex.js
CHANGED
|
@@ -152,4 +152,11 @@ export class VertexEmitter {
|
|
|
152
152
|
mapping,
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
+
/** Byte-equal invariant hook: read `instruction` back from the emitted ADK
|
|
156
|
+
* Agent Config YAML (the `# yaml-language-server` header is a YAML comment). */
|
|
157
|
+
extractInstructions(artifact) {
|
|
158
|
+
const config = yaml.load(artifact);
|
|
159
|
+
const value = config?.instruction;
|
|
160
|
+
return typeof value === "string" ? value : null;
|
|
161
|
+
}
|
|
155
162
|
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# TenantPlan — the DNA Cloud tenant→Tier assignment (record plane).
|
|
2
|
+
# One doc per tenant maps it to its CURRENT Tier (free/pro/enterprise) as
|
|
3
|
+
# first-class GLOBAL data, so enforcement follows billing state without a
|
|
4
|
+
# redeploy. This is the billing→enforcement BRIDGE: dna-cloud's Stripe webhook
|
|
5
|
+
# writes this doc on subscribe/cancel, and the MCP server reads it via
|
|
6
|
+
# kernel.tenant_plan(tenant) when a token carries no explicit plan claim.
|
|
7
|
+
#
|
|
8
|
+
# CONTRACT — the OSS SDK only READS: zero Stripe/billing code lives here. The
|
|
9
|
+
# SDK resolves tier_id from THIS doc; dna-cloud (closed source) is the only
|
|
10
|
+
# writer. A missing TenantPlan is not an error — the MCP guard falls back to the
|
|
11
|
+
# Free floor.
|
|
12
|
+
#
|
|
13
|
+
# TenantPlans live in the `_lib` scope (`tenant-plans/<tenant>.yaml`) —
|
|
14
|
+
# TenantPlan is GLOBAL (base only, no per-tenant override) and NOT inheritable;
|
|
15
|
+
# kernel.tenant_plan() queries `_lib` directly regardless of the caller's scope.
|
|
16
|
+
#
|
|
17
|
+
# PARITY-CRITICAL package data: the byte-identical mirror lives at
|
|
18
|
+
# packages/sdk-{py,ts}/{dna/extensions,src/extensions}/cloud/kinds/
|
|
19
|
+
# tenant-plan.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
|
|
20
|
+
apiVersion: github.com/ruinosus/dna/core/v1
|
|
21
|
+
kind: KindDefinition
|
|
22
|
+
metadata:
|
|
23
|
+
name: tenant-plan
|
|
24
|
+
spec:
|
|
25
|
+
target_api_version: github.com/ruinosus/dna/cloud/v1
|
|
26
|
+
target_kind: TenantPlan
|
|
27
|
+
alias: cloud-tenant-plan
|
|
28
|
+
origin: github.com/ruinosus/dna/cloud
|
|
29
|
+
tenant_scope: global
|
|
30
|
+
plane: record
|
|
31
|
+
prompt_target: false
|
|
32
|
+
flatten_in_context: false
|
|
33
|
+
prompt_target_priority: 0
|
|
34
|
+
storage:
|
|
35
|
+
type: yaml
|
|
36
|
+
container: tenant-plans
|
|
37
|
+
schema:
|
|
38
|
+
type: object
|
|
39
|
+
additionalProperties: false
|
|
40
|
+
required:
|
|
41
|
+
- tenant
|
|
42
|
+
- tier_id
|
|
43
|
+
properties:
|
|
44
|
+
tenant:
|
|
45
|
+
type: string
|
|
46
|
+
description: The DNA tenant this assignment is for. The doc name SHOULD
|
|
47
|
+
equal the tenant; kernel.tenant_plan() matches on this field.
|
|
48
|
+
tier_id:
|
|
49
|
+
type: string
|
|
50
|
+
description: The assigned Tier's id, e.g. free, pro, enterprise. Resolved
|
|
51
|
+
to caps via kernel.tier(tier_id) — never a literal in code.
|
|
52
|
+
source:
|
|
53
|
+
type: string
|
|
54
|
+
description: Where the assignment came from, e.g. stripe / manual / trial.
|
|
55
|
+
stripe_customer_id:
|
|
56
|
+
type: string
|
|
57
|
+
description: The Stripe customer id backing the assignment (dna-cloud
|
|
58
|
+
writes it; the OSS SDK never calls Stripe).
|
|
59
|
+
stripe_subscription_id:
|
|
60
|
+
type: string
|
|
61
|
+
description: The Stripe subscription id backing the assignment (dna-cloud
|
|
62
|
+
writes it; the OSS SDK never calls Stripe).
|
|
63
|
+
status:
|
|
64
|
+
type: string
|
|
65
|
+
description: The billing status of the assignment, e.g. active / past_due
|
|
66
|
+
/ canceled.
|
|
67
|
+
updated_at:
|
|
68
|
+
type: string
|
|
69
|
+
format: date-time
|
|
70
|
+
description: When dna-cloud last wrote this assignment (ISO 8601).
|
|
71
|
+
notes:
|
|
72
|
+
type:
|
|
73
|
+
- string
|
|
74
|
+
- 'null'
|
|
75
|
+
description: Free-form operator notes.
|
|
76
|
+
describe: '{tenant} → {tier_id}'
|
|
77
|
+
summary:
|
|
78
|
+
tenant: ''
|
|
79
|
+
tier_id: ''
|
|
80
|
+
status: ''
|
|
81
|
+
ui:
|
|
82
|
+
mode: govern
|
|
83
|
+
label:
|
|
84
|
+
en: Tenant Plans
|
|
85
|
+
pt-BR: Planos por Tenant
|
|
86
|
+
icon: 🎫
|
|
87
|
+
description:
|
|
88
|
+
en: DNA Cloud tenant→Tier assignments (billing→enforcement bridge).
|
|
89
|
+
pt-BR: Atribuições tenant→Tier do DNA Cloud (ponte cobrança→enforcement).
|
|
90
|
+
routes:
|
|
91
|
+
list: docs/TenantPlan
|
|
92
|
+
detail: docs/TenantPlan/:name
|
|
93
|
+
permissions:
|
|
94
|
+
list: any
|
|
95
|
+
detail: any
|
|
96
|
+
in_sidebar: true
|
|
97
|
+
display_order: 55
|
|
98
|
+
ascii_icon: 🎫
|
|
99
|
+
display_label: Tenant Plans
|
|
100
|
+
docs: A TenantPlan maps one DNA tenant to its current Tier as GLOBAL
|
|
101
|
+
declarative data, so enforcement follows billing state without a redeploy.
|
|
102
|
+
dna-cloud's Stripe webhook writes it on subscribe/cancel; the MCP server
|
|
103
|
+
reads it via kernel.tenant_plan(tenant) when the token carries no explicit
|
|
104
|
+
plan claim — zero Stripe/billing code lives in the OSS SDK, which only reads
|
|
105
|
+
the assignment.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# Tier — DNA Cloud pricing-tier registry (record plane).
|
|
2
|
+
# One doc per plan (free/pro/enterprise): hard caps (calls_per_day, rate_per_sec,
|
|
3
|
+
# max_tenants) + which feature families it unlocks + price, as first-class GLOBAL
|
|
4
|
+
# data so a limit change is a file edit, never a redeploy. NOT named `Plan` — that
|
|
5
|
+
# alias belongs to the SDLC implementation-plan Kind; a pricing plan is a Tier.
|
|
6
|
+
#
|
|
7
|
+
# CONTRACT — never hardcode caps: the quota enforcer reads calls_per_day /
|
|
8
|
+
# rate_per_sec / max_tenants from THIS registry via kernel.tier(id_or_alias); a
|
|
9
|
+
# cap literal in code is a bug.
|
|
10
|
+
#
|
|
11
|
+
# Tiers live in the `_lib` scope (`tiers/<tier_id>.yaml`) — Tier is GLOBAL (base
|
|
12
|
+
# only, no per-tenant override) and NOT inheritable; kernel.tier() queries `_lib`
|
|
13
|
+
# directly regardless of the caller's scope.
|
|
14
|
+
#
|
|
15
|
+
# PARITY-CRITICAL package data: the byte-identical mirror lives at
|
|
16
|
+
# packages/sdk-{py,ts}/{dna/extensions,src/extensions}/cloud/kinds/
|
|
17
|
+
# tier.kind.yaml (tests/test_descriptor_hash_parity.py enforces).
|
|
18
|
+
apiVersion: github.com/ruinosus/dna/core/v1
|
|
19
|
+
kind: KindDefinition
|
|
20
|
+
metadata:
|
|
21
|
+
name: tier
|
|
22
|
+
spec:
|
|
23
|
+
target_api_version: github.com/ruinosus/dna/cloud/v1
|
|
24
|
+
target_kind: Tier
|
|
25
|
+
alias: cloud-tier
|
|
26
|
+
origin: github.com/ruinosus/dna/cloud
|
|
27
|
+
tenant_scope: global
|
|
28
|
+
plane: record
|
|
29
|
+
prompt_target: false
|
|
30
|
+
flatten_in_context: false
|
|
31
|
+
prompt_target_priority: 0
|
|
32
|
+
storage:
|
|
33
|
+
type: yaml
|
|
34
|
+
container: tiers
|
|
35
|
+
schema:
|
|
36
|
+
type: object
|
|
37
|
+
additionalProperties: false
|
|
38
|
+
required:
|
|
39
|
+
- tier_id
|
|
40
|
+
- display_name
|
|
41
|
+
properties:
|
|
42
|
+
tier_id:
|
|
43
|
+
type: string
|
|
44
|
+
description: Canonical tier id, e.g. free, pro, enterprise. The doc name
|
|
45
|
+
SHOULD equal the tier_id; kernel.tier() matches on this field first.
|
|
46
|
+
display_name:
|
|
47
|
+
type: string
|
|
48
|
+
description: Human-facing plan name, e.g. Free, Pro, Enterprise.
|
|
49
|
+
price_usd_month:
|
|
50
|
+
type: number
|
|
51
|
+
default: 0
|
|
52
|
+
description: Flat monthly price in USD (0 for the free tier).
|
|
53
|
+
calls_per_day:
|
|
54
|
+
type:
|
|
55
|
+
- integer
|
|
56
|
+
- 'null'
|
|
57
|
+
description: Daily call quota. Null = unlimited (enterprise). THE value
|
|
58
|
+
the quota enforcer reads — never hardcode it in code.
|
|
59
|
+
rate_per_sec:
|
|
60
|
+
type:
|
|
61
|
+
- integer
|
|
62
|
+
- 'null'
|
|
63
|
+
description: Per-second rate limit. Null = unmetered.
|
|
64
|
+
max_tenants:
|
|
65
|
+
type:
|
|
66
|
+
- integer
|
|
67
|
+
- 'null'
|
|
68
|
+
description: Number of tenants the plan allows. Null = unlimited.
|
|
69
|
+
feature_families:
|
|
70
|
+
type: array
|
|
71
|
+
items:
|
|
72
|
+
type: string
|
|
73
|
+
description: Tool families this tier unlocks, e.g.
|
|
74
|
+
[definitions, sdlc, memory, emit].
|
|
75
|
+
memory_mode:
|
|
76
|
+
type: string
|
|
77
|
+
enum:
|
|
78
|
+
- none
|
|
79
|
+
- read
|
|
80
|
+
- write
|
|
81
|
+
default: none
|
|
82
|
+
description: Memory access level granted by the tier — none, read, or
|
|
83
|
+
write.
|
|
84
|
+
overage_per_1k_usd:
|
|
85
|
+
type:
|
|
86
|
+
- number
|
|
87
|
+
- 'null'
|
|
88
|
+
description: USD charged per 1k calls above the daily quota. Null = no
|
|
89
|
+
overage (hard cap).
|
|
90
|
+
sla:
|
|
91
|
+
type: boolean
|
|
92
|
+
default: false
|
|
93
|
+
description: True when the tier includes a support/uptime SLA
|
|
94
|
+
(enterprise).
|
|
95
|
+
aliases:
|
|
96
|
+
type: array
|
|
97
|
+
items:
|
|
98
|
+
type: string
|
|
99
|
+
description: Alternate ids that resolve to this tier (legacy plan names).
|
|
100
|
+
kernel.tier() matches these on pass 2.
|
|
101
|
+
notes:
|
|
102
|
+
type:
|
|
103
|
+
- string
|
|
104
|
+
- 'null'
|
|
105
|
+
description: Free-form operator notes.
|
|
106
|
+
describe: '{display_name} (${price_usd_month}/mo)'
|
|
107
|
+
summary:
|
|
108
|
+
tier_id: ''
|
|
109
|
+
display_name: ''
|
|
110
|
+
price_usd_month: 0
|
|
111
|
+
calls_per_day: null
|
|
112
|
+
ui:
|
|
113
|
+
mode: govern
|
|
114
|
+
label:
|
|
115
|
+
en: Tiers
|
|
116
|
+
pt-BR: Planos
|
|
117
|
+
icon: 💳
|
|
118
|
+
description:
|
|
119
|
+
en: DNA Cloud pricing tiers (caps + feature families).
|
|
120
|
+
pt-BR: Planos do DNA Cloud (limites + famílias de features).
|
|
121
|
+
routes:
|
|
122
|
+
list: docs/Tier
|
|
123
|
+
detail: docs/Tier/:name
|
|
124
|
+
permissions:
|
|
125
|
+
list: any
|
|
126
|
+
detail: any
|
|
127
|
+
in_sidebar: true
|
|
128
|
+
display_order: 54
|
|
129
|
+
ascii_icon: 💳
|
|
130
|
+
display_label: Tiers
|
|
131
|
+
docs: A Tier declares one DNA Cloud plan's hard caps (calls/day, rate, tenants)
|
|
132
|
+
and which feature families it unlocks, as GLOBAL declarative data so changing
|
|
133
|
+
a limit is a file edit, not a redeploy. Resolve it via
|
|
134
|
+
kernel.tier(id_or_alias); the quota enforcer reads the caps from here and
|
|
135
|
+
never hardcodes them.
|