Skip to content

Python 接口 ​

能直接从 noeta 导入的名字(noeta/__init__.py)及其签名,按用途分组。它们在第一次用到时才加载, 所以 import noeta 很轻。其余的从各自的模块导入(noeta.core.*、noeta.toolkit.*、 noeta.plugins.*、noeta.backends.*、noeta.host.*、noeta.agents);noeta.cli 是 noeta 命令本身,不属于 SDK。完整示例见在 Python 里嵌入;写插件那一侧见 Extension 接口。

构建各个部分 ​

noeta.store(url) -> Store ​

按地址的 scheme 通过 noeta.stores entry point 找到存储:sqlite:///path(会展开 ~,自动建 上级目录)、postgres://… 或 postgresql://…(需要 postgres 组件)。不认识的 scheme 抛 ValueError。

noeta.provider(family, **kw) -> Provider ​

按类型找 provider 适配器(noeta.providers entry point:anthropic、openai_compat、 openai_responses,或第三方的)。关键字参数交给它的工厂函数;内置的接受 api_key=、base_url= 和 extra_headers=。openai_compat 必须给 base_url(接口根地址,后面会拼上 /chat/completions,比如 …/v1);openai_responses 必须 同时给 base_url(完整的 …/v1/responses 地址)和 api_key。不给 api_key 时,anthropic 读 ANTHROPIC_API_KEY,openai_compat 读 OPENAI_API_KEY;anthropic 还接受 prompt_cache_ttl="1h"。

noeta.default_agents(model, *, plugins=None, add=None, extensions=(), extra_files=(), made=None, **main_kw) -> list[Agent] ​

main 和子代理 Explore、Plan、general-purpose,由 noeta/agents/files/ 里的代理文件构建, 再加上 extra_files 里的代理(和内置代理同名的会替换掉内置的)。

参数含义
model所有代理用的模型,除非代理文件另有指定;写了 model: inherit(或没写 model)的代理用它,作为子代理时用调用方的模型
plugins按插件名:它的参数(映射;enabled: False 表示关掉)、一个现成的 Extension,或 False(从所有代理里去掉)。memory、mcp、repetition_guard 要在这里写上设置才开
add按代理名("*" 表示全部):在文件自带的插件之后再加,写成 (名字, 参数) 对
extensions加给每个代理的 Extension 实例
extra_files更多 AgentFile(来自 read_agent_dir / parse_agent_file)
made一个字典,记住创建过的插件,第二次构建时复用没变的
**main_kw要替换的 main 字段,如 one_shot=True、system_prompt=…、name=…

noeta.parse_agent_file(text, *, source="", default_name="") -> AgentFile ​

读一个代理文件:YAML frontmatter 用 Claude Code 的字段(外加 plugins、one_shot 和 identity),正文是系统 提示词。文件格式不对时抛 AgentFileError。

noeta.read_agent_dir(path) -> list[AgentFile] ​

path 下的每个 *.md 代理文件(不递归)。读不了的文件记日志后跳过。

noeta.Agent ​

一个 dataclass,是 Runtime 运行的对象。

字段默认含义
name—代理名,任务里记的就是它
model—模型 id
system_prompt""系统提示词(提示词名 system/<agent>)
identity""一行身份说明,作为单独的缓存块放在系统提示词前面
description""它在别的代理的子代理清单里那一行
extensions()它的插件
tools()插件之外它自己的工具
effortNonelow … max
max_tokensNone输出上限;None 取模型目录里的
one_shotFalse第一轮结束就结束任务,而不是等下一条消息
output_schemaNone任务没设时,回答要遵循的 JSON Schema
step_budget1000一轮里允许调用模型的次数;用完后的下一次要求关掉工具给出最终回答
extra{}代理文件的其他字段,原样保留
allowed_toolsNone只提供这些工具名(None 表示全部)
denied_tools()永远不提供的工具名

all_extensions() 返回任务实际运行的插件列表:extensions,前面再加上把 tools 包成的 ToolSet;有两个插件同名或有插件没名字时抛 ValueError。

运行任务 ​

noeta.Runtime ​

即 worker:从存储里认领任务并运行,可以同时跑很多个。一个 dataclass:

字段默认含义
store—存储
agents—dict[名字, Agent]
provider_for—模型 id -> Provider
env_for本机、任务的 cwdTaskState -> ExecEnv:任务的工具在哪个执行环境里跑
task_providerNone(state, model) -> Provider,用来遵循任务配置里记下的路由
prepare_childNone子任务的 TaskCreated 写入之前由宿主补全
worker主机:pid:随机串这个 worker 在认领记录里的名字
queues("default",)从哪些队列认领
lease_seconds30.0一次认领不续约能维持多久
poll_interval0.5没有变化通知时,多久看一次有没有可认领的任务(秒)
concurrency8同时跑几个任务
spill_rootNone太长、没法直接返回的工具输出存在哪;None 表示截断
prompt_dirs()宿主的提示词覆盖文件目录
workspace_trustedNonecwd -> bool:任务的 .noeta/prompts 能不能覆盖提示词(None 表示都能)
on_deltaNone实时输出回调 (task_id, Delta) -> None
output_ceiling模型目录给的代理没设时 模型 -> max_tokens
snapshot_every100每隔多少个事件写一次重放快照;0 表示不写
agent_aliases{}代理的旧名字 -> 现在的名字,给用旧名记录的任务用
rootsNone只服务这些任务树(一个集合,前端往里加);None 表示服务所有任务
sleepasyncio.sleep模型调用重试之间怎么等(测试里换成假的)

方法:

方法作用
await run_until_idle()不断认领并运行任务,直到没有可认领的
await run_once() -> bool认领一个任务,跑到它开始等待或结束;没有可认领的返回 False
await serve(stop: asyncio.Event)有任务可认领就跑,直到 stop 被设置;退出时正在跑的任务会交还
await let_go()放掉在等待期间保留着的认领(任务持有后台作业时);任务仍处于等待状态
await preview(task_id) -> ModelRequest | None任务下一次调用模型时的请求起点,不记录任何东西
serve_root(root_id)把一棵任务树加进 roots,并立刻找活干
agent(name) -> Agent | None按名字或旧名字找代理
provider_of(state, model) -> Provider任务这次调用发给哪个 provider:设了 task_provider 就用它,否则用 provider_for
await run_claim(claim)运行调用方已经拿到的一次认领,直到任务开始等待或结束

noeta.LocalApp(store, agents, provider, *, own_tasks_only=False, task_config=None, channels=(), **runtime_kw) ​

存储、在后台跑的 Runtime 和客户端,全在一个进程里:终端界面、noeta acp、noeta -p、noeta worker 用的就是它 (noeta/host/local.py)。

参数含义
agentsAgent 列表
provider一个 Provider,或 模型 -> Provider 函数
own_tasks_only只服务自己创建或接手的任务树
task_config垫在每个新任务配置下面(prompts 按键合并)
channels启动后一起运行的渠道(Channel)
**runtime_kw传给 Runtime(spill_root 默认是系统临时目录下的 noeta-<uid>/tool-results;poll_interval 默认 0.2,客户端也按这个间隔轮询;调用方自己传的值优先)
成员说明
client一个 LocalClient(Client 的子类),它创建的新任务由这个应用的 worker 执行;它把 task_config 垫在每个新任务下面,并把代理文件的 initialPrompt 放在新对话第一条消息前面
runtime、store各个部分
await start(worker=True) / await stop(),或 async with在后台运行 worker、渠道,以及每天一次的旧工具输出清理;停止时也会关闭存储
await run_until(stop)先 start(),一直跑到 stop(一个 asyncio.Event)被设上,或者 worker 自己出错(错误照常抛出),然后 stop():只跑任务的进程用它,noeta worker 就是这样
deltas(sink) -> 取消订阅函数实时输出:每个片段调用一次 sink(task_id, Delta),用 Postgres 时也包括其他机器的
await adopt(task_id)接手一个已有任务的任务树(续聊时用);这个应用没有那个代理时抛 UnknownAgent
replace_agents(agents)从下一次接手任务起改用这些代理
await agent_names()所有代理的名字,排好序
await commands(agent, ctx)、await run_command(agent, ctx, text)某个代理的斜杠命令
await context(task_id)任务下一次请求里装了什么,按类别统计
await summaries(limit=50, archived=None)、await archive(task_id, archived=True)给续聊列表用的根任务
notices、main_agent前端该展示给人的提示;默认用的代理("main")
channels它运行的渠道
reload_plugins、list_models、prepare_model由宿主设置的回调(noeta 命令会设):重新查找插件、列出各 provider 的模型、切换模型前先把它定下来;没设时为 None

noeta.Client(store, *, poll_interval=0.5) ​

本进程里、建在存储上的客户端接口(noeta/core/client.py)。它只写存储,活由某处的 Runtime 干。

方法作用
await create(agent, input=None, *, task_id=None, config=None, queue="default", dedupe_key=None, history_from=None, history_through=None) -> str创建任务,可以顺带第一条消息;返回任务 id。重复的 dedupe_key 或已存在的 task_id 返回已有的任务
await send(task_id, input, *, when="next_turn", dedupe_key=None) -> int发消息("now":在正在进行的这一轮的下一步送到);返回它在收件箱里的序号
await answer(task_id, request_id, decision=None, *, always=False, data=None) -> int回答审批("allow" / "deny")或提问(data);同一个请求 id 幂等
await signal(task_id, name, data=None, *, dedupe_key=None, wake=True) -> int发一个带名字的信号给插件读(permission_mode、config 等)
await cancel(task_id, reason="", *, turn_only=False)结束任务,或只停下当前这一轮
await get(task_id) -> TaskRow任务行;不存在时抛 UnknownTask
await tasks(*, parent_id=None, status=None, limit=100) -> list[TaskRow]任务表里的行
await events(task_id, after=0) -> list[Recorded]序号 after 之后的日志
watch(task_id, after=0) -> AsyncIterator[Recorded]从 after 开始实时吐出日志,任务结束为止
await wait(task_id, *, timeout=None) -> TaskRow等到任务开始等待或已结束

input 是字符串,或一个 noeta.core.types.Message(由 Text 和 Image 块组成的用户发言); 图片在存下之前会缩到合适的大小。history_from 指向的任务不存在时抛 UnknownTask。

noeta.RemoteClient(url, *, token=None, transport=None) ​

方法和 Client 相同,走 noeta serve 服务端的 HTTP 接口(noeta/host/remote.py);watch 会 自动重连。它还有 await context(task_id)(任务下一次请求里装了什么)和 await close()(关闭它的 HTTP 客户端)。transport 是 httpx 的 transport(测试用)。

noeta.connect(url, *, token=None, **kw) -> Client | RemoteClient ​

sqlite:///… 或 postgres://… 给出这个存储上的 Client;http://… / https://… 给出连那台 服务端的 RemoteClient(带 token)。多余的关键字参数交给客户端的构造函数。

记录和状态 ​

名字说明
noeta.Recorded存下来的一个事件:seq、at(Unix 时间)、event(noeta.core.events 里十种事件之一;见事件)
noeta.TaskState重放日志得到的任务状态:配置、消息、调用、等待、子任务、定时器、用量,以及每个插件自己的状态(在 ext 里,插件只读)
noeta.ToolUse模型发起的一次工具调用:id、name、input

写插件和工具 ​

下面是写插件要用的名字;每个的完整说明见 Extension 接口和写一个插件。

名字签名说明
noeta.Extension继承它,设置 name唯一的插件接口:工具、上下文、提醒、命令、工具调用前的把关、从日志重放出的状态;每个方法都可选
noeta.TaskView传给各个钩子插件眼中的任务:重放出的状态、记录条目的方法、执行环境
noeta.ToolChanges(add=(), withhold={}, entries=())Extension.tool_changes 对任务工具列表做的改动
noeta.ContextPart(text, order=100, before="")插件往系统提示词里加的文字
noeta.Reminder(text, kind="reminder", data=None, origin="system", role="user", order=100)给模型看、但不是用户说的话
noeta.Command、noeta.CommandContext(name, description, template="", argument_hint="", local=False);(cwd, task_id=None)前端提供的斜杠命令,以及它在哪里列出、执行
noeta.Allow、noeta.Ask、noeta.Deny、noeta.RewriteAllow(approve=False, outright=False)、Ask(prompt="", detail={}, firm=False)、Deny(reason="")、Rewrite(input, name=None)before_tool 的返回值:拒绝优先于改写、询问和放行
noeta.Tool一个协议:name、description、input_schema、read_only、clearable、async run(input, ctx)模型能调用的工具;可选 flags、resume、recover、rule_inputs
noeta.ToolContext传给 run这次调用的任务、执行环境、各插件状态,以及 record、spawn、post、keep、progress 等
noeta.ToolOutput(content=[], is_error=False, meta={}, display=None, park=None, end_turn=None, block_list=False)工具的结果
noeta.text_output(text, *, is_error=False, **meta) -> ToolOutput纯文字结果
noeta.function_tool@function_tool 或 @function_tool(name=None, description=None, read_only=False, clearable=False, deferred=False)把带类型标注的函数变成工具:名字、docstring、由标注生成的参数格式
noeta.askask(*questions) -> Park让工具调用挂起,向人提问({"question", "header"?, "options"?, "multiSelect"?})
noeta.read_answerread_answer(outcome, asked) -> dict | None挂起的提问拿到的回答(已校验),撤回时为 None

noeta 之外常用的模块 ​

模块用途
noeta.core.events事件类型(TaskCreated、ModelReplied、ToolStarted、Waiting、TaskEnded 等)和收件箱条目
noeta.core.typesMessage、Text、Image、ToolUse、Delta、Usage
noeta.core.models模型目录:find_spec、register_models、ModelSpec、ALIASES
noeta.host.pendingpending_tree、choices、answer_choice:一棵任务树在等人处理什么
noeta.host.questionsanswer_question:校验后回答提问
noeta.host.httpcreate_app:把 HTTP 接口作为 Starlette 应用嵌进你自己的服务
noeta.testingScriptedProvider、reply:测试用的按脚本回复的模型